Skip to content

Commit

Permalink
supports librdx crash log directory
Browse files Browse the repository at this point in the history
  • Loading branch information
mariotaku committed Nov 22, 2023
1 parent 08d81eb commit b8b6e38
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 79 deletions.
129 changes: 75 additions & 54 deletions src/app/core/services/backend-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,101 @@ import {invoke} from '@tauri-apps/api/tauri';
import {NgZone} from '@angular/core';
import {omit} from "lodash-es";
import {noop} from "rxjs";
import {ExecutionError} from "./remote-command.service";

export abstract class BackendClient {
protected constructor(protected zone: NgZone, public category: string) {
}

protected invoke<T>(method: string, args?: Record<string, unknown>): Promise<T> {
return new Promise<T>((resolve, reject) => {
const cmd = `plugin:${(this.category)}|${method}`;
console.debug('invoke', `${this.category}/${method}`, args);
invoke(cmd, args)
.then(result => {
console.debug('invoke', `${this.category}/${method}`, 'result', result);
this.zone.run(() => resolve(result as any));
})
.catch(reason => {
console.warn('invoke', `${this.category}/${method}`, 'error', reason);
this.zone.run(() => reject(new BackendError(reason)));
protected constructor(protected zone: NgZone, public category: string) {
}

protected invoke<T>(method: string, args?: Record<string, unknown>): Promise<T> {
return new Promise<T>((resolve, reject) => {
const cmd = `plugin:${(this.category)}|${method}`;
console.debug('invoke', `${this.category}/${method}`, args);
invoke(cmd, args)
.then(result => {
console.debug('invoke', `${this.category}/${method}`, 'result', result);
this.zone.run(() => resolve(result as any));
})
.catch(reason => {
console.warn('invoke', `${this.category}/${method}`, 'error', reason);
this.zone.run(() => reject(BackendClient.toBackendError(reason)));
});
});
});
}
}

protected on(method: string, handler: (..._: any[]) => void): void {
event.listen(`${this.category}/${method}`, (event) =>
this.zone.run(() => handler(event.payload))).then(noop);
}

protected on(method: string, handler: (..._: any[]) => void): void {
event.listen(`${this.category}/${method}`, (event) =>
this.zone.run(() => handler(event.payload))).then(noop);
}
private static toBackendError(e: unknown): Error {
if (BackendError.isCompatibleBody(e)) {
if (e.reason === 'ExitStatus') {
return ExecutionError.fromBackendError(e);
} else if (IOError.isCompatibleBody(e)) {
return new IOError(e);
}
return new BackendError(e);
}
return e as Error;
}

}

export interface BackendErrorBody {
reason: ErrorReason,
message?: string,
reason: ErrorReason,
message?: string,

[key: string]: unknown;
[key: string]: unknown;
}

export interface IOErrorBody extends BackendErrorBody {
code: 'PermissionDenied' | 'NotFound' | string;
}

export class BackendError extends Error {
reason: ErrorReason;
reason: ErrorReason;

[key: string]: unknown;
[key: string]: unknown;

constructor(body: BackendErrorBody) {
super(body.message ?? body.reason);
this.reason = body.reason;
Object.assign(this, omit(body, 'message', 'reason'));
}
constructor(body: BackendErrorBody) {
super(body.message ?? body.reason);
this.reason = body.reason;
Object.assign(this, omit(body, 'message', 'reason'));
}

static isCompatible(e: unknown): e is BackendError {
return e instanceof Error && BackendError.isCompatibleBody(e);
}
static isCompatible(e: unknown): e is BackendError {
return e instanceof Error && BackendError.isCompatibleBody(e);
}

static isCompatibleBody(e: unknown): e is BackendErrorBody {
return (typeof (e as any).reason === 'string');
}
static isCompatibleBody(e: unknown): e is BackendErrorBody {
return (typeof (e as any).reason === 'string');
}
}

export type ErrorReason =
'Authorization' |
'BadPassphrase' |
'Disconnected' |
'ExitStatus' |
'IO' |
'Message' |
'NeedsReconnect' |
'NegativeReply' |
'NotFound' |
'PassphraseRequired' |
'Timeout' |
'Unsupported' |
'UnsupportedKey';
'Authorization' |
'BadPassphrase' |
'Disconnected' |
'ExitStatus' |
'IO' |
'Message' |
'NeedsReconnect' |
'NegativeReply' |
'NotFound' |
'PassphraseRequired' |
'Timeout' |
'Unsupported' |
'UnsupportedKey';

export class IOError extends BackendError {
declare code: 'PermissionDenied' | 'NotFound' | string;
declare code: 'PermissionDenied' | 'NotFound' | string;

static override isCompatible(e: unknown): e is IOError {
return BackendError.isCompatible(e) && e.reason === 'IO';
}

static override isCompatible(e: unknown): e is IOError {
return BackendError.isCompatible(e) && e.reason === 'IO';
}
static override isCompatibleBody(e: unknown): e is IOErrorBody {
return BackendError.isCompatibleBody(e) && e.reason === 'IO';
}
}
27 changes: 23 additions & 4 deletions src/app/core/services/device-manager.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Injectable, NgZone} from "@angular/core";
import {BehaviorSubject, from, Observable, Subject} from "rxjs";
import {CrashReportEntry, Device, DeviceLike, FileItem, FileSession, NewDevice, StorageInfo} from '../../types';
import {BackendClient} from "./backend-client";
import {BackendClient, IOError} from "./backend-client";
import {FileSessionImpl} from "./file.session";
import {HomebrewChannelConfiguration, SystemInfo} from "../../types/luna-apis";
import {LunaResponseError, RemoteLunaService} from "./remote-luna.service";
Expand Down Expand Up @@ -75,9 +75,28 @@ export class DeviceManagerService extends BackendClient {
}

async listCrashReports(device: Device): Promise<CrashReport[]> {
const dir = '/tmp/faultmanager/crash/';
return this.file.ls(device, dir)
.then(list => list.map(l => CrashReport.obtain(this.file, device, dir, l)));
const dirs = [
'/tmp/faultmanager/crash/',
'/tmp/var/log/reports/librdx/',
];
let currentDir: string | null = null;
let files: FileItem[] | null = null;
for (const dir of dirs) {
try {
files = await this.file.ls(device, dir);
currentDir = dir;
break;
} catch (e) {
if (e instanceof IOError && e.code === 'NotFound') {
continue;
}
throw e;
}
}
if (!files || !currentDir) {
return [];
}
return files.map(l => CrashReport.obtain(this.file, device, currentDir!, l))
}

async extendDevMode(device: Device): Promise<any> {
Expand Down
27 changes: 8 additions & 19 deletions src/app/core/services/remote-file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,18 @@ export class RemoteFileService extends BackendClient {
}

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

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);
await this.cmd.exec(device, `xargs -0 rm ${recursive ? '-r' : ''}`, 'buffer', path);
}

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));
const outputData = Buffer.from(await this.invoke<Buffer>('read', {device, path, encoding}));
switch (output) {
case 'utf-8':
return outputData.toString('utf-8');
Expand All @@ -40,24 +38,23 @@ export class RemoteFileService extends BackendClient {
}

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

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

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

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

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

public async serveLocal(device: Device, localPath: string): Promise<ServeInstance> {
Expand Down Expand Up @@ -111,14 +108,6 @@ export class RemoteFileService extends BackendClient {
});
}

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

export declare interface ServeInstance {
Expand Down
10 changes: 8 additions & 2 deletions src/app/debug/crashes/crashes.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<div class="w-100 h-100 p-4 overflow-y-auto" *ngIf="!reportsError else error">
<ol class="list-group list-group-numbered">
<div class="w-100 h-100 p-3 overflow-y-auto" *ngIf="!reportsError else error" [ngSwitch]="reports?.length">
<div class="card m-1" *ngSwitchCase="0">
<div class="card-body">
<h5 class="card-title">No crash reports</h5>
<p class="card-text">If a native application crashes, the crash report will be available here.</p>
</div>
</div>
<ol class="list-group list-group-numbered" *ngSwitchDefault>
<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">
Expand Down

0 comments on commit b8b6e38

Please sign in to comment.