Skip to content

Commit

Permalink
supports delete crash report
Browse files Browse the repository at this point in the history
  • Loading branch information
mariotaku committed Sep 8, 2023
1 parent 58a7f47 commit dc90a4d
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 240 deletions.
43 changes: 18 additions & 25 deletions src/app/core/services/device-manager.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import {Injectable, NgZone} from "@angular/core";
import {BehaviorSubject, from, Observable, Subject} from "rxjs";
import {CrashReportEntry, Device, DeviceLike, FileSession, NewDevice, StorageInfo} from '../../types';
import {CrashReportEntry, Device, DeviceLike, FileItem, FileSession, NewDevice, StorageInfo} from '../../types';
import {BackendClient} from "./backend-client";
import {FileSessionImpl} from "./file.session";
import {HomebrewChannelConfiguration, SystemInfo} from "../../types/luna-apis";
import {basename} from "@tauri-apps/api/path";
import {LunaResponseError, RemoteLunaService} from "./remote-luna.service";
import {RemoteCommandService} from "./remote-command.service";
import {Buffer} from "buffer";
import {RemoteFileService} from "./remote-file.service";
import {DevModeService} from "./dev-mode.service";

Expand Down Expand Up @@ -77,20 +75,9 @@ export class DeviceManagerService extends BackendClient {
}

async listCrashReports(device: Device): Promise<CrashReport[]> {
return this.cmd.exec(device, 'find /tmp/faultmanager/crash/ -name \'*.gz\' -print0', 'utf-8')
.catch((e) => {
if (e.data) {
throw new Error(e.data);
} else {
throw e;
}
})
.then(output => output.split('\0').filter(l => l.length))
.then(list => Promise.all(list.map(l => CrashReport.obtain(this, device, l))));
}

async zcat(device: Device, path: string): Promise<Buffer> {
return await this.cmd.exec(device, `xargs -0 zcat`, 'buffer', path);
const dir = '/tmp/faultmanager/crash/';
return this.file.ls(device, dir)
.then(list => list.map(l => CrashReport.obtain(this.file, device, dir, l)));
}

async extendDevMode(device: Device): Promise<any> {
Expand Down Expand Up @@ -156,18 +143,24 @@ export class DeviceManagerService extends BackendClient {

export class CrashReport implements CrashReportEntry {

constructor(public device: Device, public path: string, public title: string, public summary: string,
public saveName: string, public content: Observable<string>) {
constructor(public device: Device, public dir: string, public file: FileItem, public title: string,
public summary: string, public saveName: string, public content: Observable<string>) {
}

get path(): string {
return `${this.dir}/${this.file.filename}`;
}

static async obtain(dm: DeviceManagerService, device: Device, path: string) {
const {title, summary, saveName} = await CrashReport.parseTitle(path);
const content = from(dm.zcat(device, path).then(content => content.toString('utf-8').trim()));
return new CrashReport(device, path, title, summary, saveName, content);
static obtain(fs: RemoteFileService, device: Device, dir: string, info: FileItem) {
const {title, summary, saveName} = CrashReport.parseTitle(info.filename);
const path = `${dir}/${info.filename}`;
const content = from(fs.read(device, path, 'gzip', 'utf-8')
.then(s => s.trim()));
return new CrashReport(device, dir, info, title, summary, saveName, content);
}

private static async parseTitle(path: string): Promise<{ title: string, summary: string; saveName: string; }> {
const name = (await basename(path)).replace(/[\x00-\x1f]/g, '/').replace(/.gz$/, '');
private static parseTitle(filename: string): { title: string, summary: string; saveName: string; } {
const name = filename.replace(/[\x00-\x1f]/g, '/').replace(/.gz$/, '');
let appDirIdx = -1, appDirPrefix = '';
for (const prefix of ['/usr/palm/applications/', '/var/palm/jail/']) {
appDirIdx = name.indexOf(prefix);
Expand Down
218 changes: 109 additions & 109 deletions src/app/core/services/remote-file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,127 +8,127 @@ import {EventChannel} from "../event-channel";
import {map} from "rxjs/operators";

@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class RemoteFileService extends BackendClient {

constructor(zone: NgZone, private cmd: RemoteCommandService) {
super(zone, 'remote-file');
}

public async ls(device: Device, path: string): Promise<FileItem[]> {
return this.invoke<FileItem[]>('ls', {device, path}).catch(RemoteFileService.handleExecError);
}

public async rm(device: Device, path: string, recursive: boolean): Promise<void> {
await this.cmd.exec(device, `xargs -0 rm ${recursive ? '-r' : ''}`, 'buffer', path)
.catch(RemoteFileService.handleExecError);
}

public async read(device: Device, path: string, output?: 'buffer'): Promise<Buffer>;
public async read(device: Device, path: string, output: 'utf-8'): Promise<string>;

public async read(device: Device, path: string, output?: 'buffer' | 'utf-8'): Promise<Buffer | string> {
const outputData = Buffer.from(await this.invoke<Buffer>('read', {device, path})
.catch(RemoteFileService.handleExecError));
switch (output) {
case 'utf-8':
return outputData.toString('utf-8');
default:
return outputData;
constructor(zone: NgZone, private cmd: RemoteCommandService) {
super(zone, 'remote-file');
}
}

public async write(device: Device, path: string, content?: string | Uint8Array): Promise<void> {
await this.invoke('write', {device, path, content}).catch(RemoteFileService.handleExecError);
}

public async get(device: Device, path: string, target: string): Promise<void> {
await this.invoke('get', {device, path, target}).catch(RemoteFileService.handleExecError);
}

public async put(device: Device, path: string, source: string): Promise<void> {
await this.invoke('put', {device, path, source}).catch(RemoteFileService.handleExecError);
}

public async mkdir(device: Device, path: string): Promise<void> {
await this.cmd.exec(device, `xargs -0 mkdir`, 'buffer', path)
.catch(RemoteFileService.handleExecError);
}

public async getTemp(device: Device, path: string): Promise<string> {
return await this.invoke<string>('get_temp', {device, path}).catch(RemoteFileService.handleExecError);
}

public async serveLocal(device: Device, localPath: string): Promise<ServeInstance> {
const subject = new Subject<Record<string, any>>();
const token = await this.invoke<string>('serve', {device, path: localPath});
const channel = new class extends EventChannel<Record<string, any>, any> {
constructor(token: string) {
super(token);
}

onClose(payload: any): void {
console.log('serve closed', payload);
if (payload) {
if (BackendError.isCompatibleBody(payload)) {
if (payload.reason === 'ExitStatus') {
subject.error(ExecutionError.fromBackendError(payload));
} else {
subject.error(new BackendError(payload));
}
} else {
subject.error(payload);
}
} else {
subject.complete();

public async ls(device: Device, path: string): Promise<FileItem[]> {
return this.invoke<FileItem[]>('ls', {device, path}).catch(RemoteFileService.handleExecError);
}

public async rm(device: Device, path: string, recursive: boolean): Promise<void> {
await this.cmd.exec(device, `xargs -0 rm ${recursive ? '-r' : ''}`, 'buffer', path)
.catch(RemoteFileService.handleExecError);
}

public async read(device: Device, path: string, encoding?: 'gzip', output?: 'buffer'): Promise<Buffer>;
public async read(device: Device, path: string, encoding?: 'gzip', output?: 'utf-8'): Promise<string>;

public async read(device: Device, path: string, encoding?: 'gzip', output?: 'buffer' | 'utf-8'): Promise<Buffer | string> {
const outputData = Buffer.from(await this.invoke<Buffer>('read', {device, path, encoding})
.catch(RemoteFileService.handleExecError));
switch (output) {
case 'utf-8':
return outputData.toString('utf-8');
default:
return outputData;
}
}

onReceive(payload: Record<string, any>): void {
subject.next(payload);
}
}(token);
await channel.send();
return firstValueFrom(subject).then((v: Record<string, any>): ServeInstance => {
return {
host: v['host'],
requests: subject.pipe(map(v => v as ServeRequest), finalize(() => channel.unlisten())),
async interrupt(): Promise<void> {
await channel.close();
await lastValueFrom(subject).catch(e => {
if (e.name === 'EmptyError') {
return null;
} else {
throw e
}

public async write(device: Device, path: string, content?: string | Uint8Array): Promise<void> {
await this.invoke('write', {device, path, content}).catch(RemoteFileService.handleExecError);
}

public async get(device: Device, path: string, target: string): Promise<void> {
await this.invoke('get', {device, path, target}).catch(RemoteFileService.handleExecError);
}

public async put(device: Device, path: string, source: string): Promise<void> {
await this.invoke('put', {device, path, source}).catch(RemoteFileService.handleExecError);
}

public async mkdir(device: Device, path: string): Promise<void> {
await this.cmd.exec(device, `xargs -0 mkdir`, 'buffer', path)
.catch(RemoteFileService.handleExecError);
}

public async getTemp(device: Device, path: string): Promise<string> {
return await this.invoke<string>('get_temp', {device, path}).catch(RemoteFileService.handleExecError);
}

public async serveLocal(device: Device, localPath: string): Promise<ServeInstance> {
const subject = new Subject<Record<string, any>>();
const token = await this.invoke<string>('serve', {device, path: localPath});
const channel = new class extends EventChannel<Record<string, any>, any> {
constructor(token: string) {
super(token);
}
});
},
}
}).catch(e => {
channel.unlisten();
throw e;
});
}

private static handleExecError(e: unknown): never {
if (BackendError.isCompatible(e)) {
if (e.reason === 'ExitStatus') {
throw ExecutionError.fromBackendError(e);
}

onClose(payload: any): void {
console.log('serve closed', payload);
if (payload) {
if (BackendError.isCompatibleBody(payload)) {
if (payload.reason === 'ExitStatus') {
subject.error(ExecutionError.fromBackendError(payload));
} else {
subject.error(new BackendError(payload));
}
} else {
subject.error(payload);
}
} else {
subject.complete();
}
}

onReceive(payload: Record<string, any>): void {
subject.next(payload);
}
}(token);
await channel.send();
return firstValueFrom(subject).then((v: Record<string, any>): ServeInstance => {
return {
host: v['host'],
requests: subject.pipe(map(v => v as ServeRequest), finalize(() => channel.unlisten())),
async interrupt(): Promise<void> {
await channel.close();
await lastValueFrom(subject).catch(e => {
if (e.name === 'EmptyError') {
return null;
} else {
throw e
}
});
},
}
}).catch(e => {
channel.unlisten();
throw e;
});
}

private static handleExecError(e: unknown): never {
if (BackendError.isCompatible(e)) {
if (e.reason === 'ExitStatus') {
throw ExecutionError.fromBackendError(e);
}
}
throw e;
}
throw e;
}
}

export declare interface ServeInstance {
host: string;
requests: Observable<ServeRequest>;
host: string;
requests: Observable<ServeRequest>;

interrupt(): Promise<void>;
interrupt(): Promise<void>;
}

export declare interface ServeRequest {
path: string;
status: 200 | 404;
path: string;
status: 200 | 404;
}
8 changes: 7 additions & 1 deletion src/app/debug/crashes/crashes.component.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<div class="w-100 h-100 p-4" *ngIf="!reportsError else error">
<ol class="list-group list-group-numbered">
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-start"
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
(click)="openDetails(report)" *ngFor="let report of reports">
<div class="ms-2 me-auto">
<div class="fw-bold">{{report.title}}</div>
{{report.summary}}
</div>
<div>
<button class="btn btn-link text-danger" type="button" (click)="$event.stopPropagation();deleteReport(report)"
*ngIf="report.file.access?.write">
<i class="bi bi-trash"></i>
</button>
</div>
</li>
</ol>
</div>
Expand Down
Loading

0 comments on commit dc90a4d

Please sign in to comment.