refactore: create feature modules (#387)

* add @angular/cli to the project

* increase nodejs version -> 8.2.1

* add lerna

* merge web and shared module

* move electron module into packages as uhk-agent

Electron agent functionality is not working

* delete symlinker

* convert private properties to public of component if used in html

* revert uhk-message.component

* fix component path

* fix the correct name of the uhk-message.component.scss

* building web and electron module

* delete uhk-renderer package

* handle device connect disconnect state

* add privilege detection

* fix set privilege functionality

* turn back download keymap functionality

* add bootstrap, select2 js and fix null pointer exception

* turn back upload data to keyboard

* fix send keymap

* fix test-serializer

* add missing package.json

* merging

* fix appveyor build

* fix linting

* turn back electron storage service

* commit the missing electron-datastorage-repository

* update node to 8.3.0 in .nvmrc and log node version in appveyor build

* set exact version number in appveyor build

* vertical align privilege and missing device components

* set back node version to 8 in appveyor

* move node-usb dependency from usb dir to root

maybe it is fix the appveyor build

* revert usb to root

* fix electron builder script

* fix electron builder script

* turn off electron devtools

* remove CTRL+U functionality

* fix CTRL+o

* fix lint error

* turnoff store freeze

* start process when got `Error: EPERM: operation not permitted` error

* move files from root usb dir -> packages/usb
This commit is contained in:
Róbert Kiss
2017-08-19 20:02:17 +02:00
committed by László Monda
parent 97770f67c0
commit 0f558e4132
524 changed files with 25606 additions and 5036 deletions

View File

@@ -0,0 +1,42 @@
import * as storage from 'electron-settings';
import { UserConfiguration } from '../../app/config-serializer/config-items/user-configuration';
import { DataStorageRepositoryService } from '../../app/services/datastorage-repository.service';
import { AutoUpdateSettings } from '../../app/models/auto-update-settings';
export class ElectronDataStorageRepositoryService implements DataStorageRepositoryService {
static getValue(key: string): any {
const value = storage.get(key);
if (!value) {
return null;
}
return JSON.parse(<string>value);
}
static saveValue(key: string, value: any) {
storage.set(key, JSON.stringify(value));
}
// TODO: Throw error when read user config from electron datastore
// Agent-electron should always read the configuration from the UHK over USB which will be implemented later.
// If implemented the feature should have to throw an error to prevent unwanted side effects.
getConfig(): UserConfiguration {
return null;
}
// TODO: Throw error when save user config from electron-datastore
// Agent-electron should always read the configuration from the UHK over USB which will be implemented later.
// If implemented the feature should have to throw an error to prevent unwanted side effects.
saveConfig(config: UserConfiguration): void {
}
getAutoUpdateSettings(): AutoUpdateSettings {
return ElectronDataStorageRepositoryService.getValue('auto-update-settings');
}
saveAutoUpdateSettings(settings: AutoUpdateSettings): void {
ElectronDataStorageRepositoryService.saveValue('auto-update-settings', settings);
}
}

View File

@@ -0,0 +1,11 @@
import { ErrorHandler, Injectable } from '@angular/core';
import { LogService } from 'uhk-common';
@Injectable()
export class ElectronErrorHandlerService implements ErrorHandler {
constructor(private logService: LogService) { }
handleError(error: any) {
this.logService.error(error);
}
}

View File

@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';
import * as log from 'electron-log';
import * as util from 'util';
import { LogService } from 'uhk-common';
/**
* This service use the electron-log package to write log in file.
* The logger usable in main and renderer process.
* The location of the log files:
* - on Linux: ~/.config/<app name>/log.log
* - on OS X: ~/Library/Logs/<app name>/log.log
* - on Windows: %USERPROFILE%\AppData\Roaming\<app name>\log.log
* The app name: UHK Agent. The up to date value in the scripts/release.js file.
*/
@Injectable()
export class ElectronLogService implements LogService {
private static getErrorText(args: any) {
return util.inspect(args);
}
error(...args: any[]): void {
log.error(ElectronLogService.getErrorText(args));
}
debug(...args: any[]): void {
log.debug(ElectronLogService.getErrorText(args));
}
silly(...args: any[]): void {
log.silly(ElectronLogService.getErrorText(args));
}
info(...args: any[]): void {
log.info(ElectronLogService.getErrorText(args));
}
}

View File

@@ -0,0 +1,15 @@
import { ipcRenderer } from 'electron';
import { IpcCommonRenderer } from '../../app/services/ipc-common-renderer';
export class IpcUhkRenderer implements IpcCommonRenderer {
public send(channel: string, ...args: any[]): void {
ipcRenderer.send(channel, args);
}
public on(channel: string, listener: Function): this {
ipcRenderer.on(channel, listener);
return this;
}
}

View File

@@ -0,0 +1,17 @@
import { LogService } from 'uhk-common';
import { UhkDeviceService } from './uhk-device.service';
import { UhkHidApiService } from './uhk-hid-api.service';
export function uhkDeviceFactory(logService: LogService): UhkDeviceService {
// HID API officially support MAC, WIN and linux x64 platform
// https://github.com/node-hid/node-hid#platform-support
if (process.platform === 'darwin' ||
process.platform === 'win32' ||
(process.platform === 'linux' && process.arch === 'x64')) {
return new UhkHidApiService(logService);
}
// On other platform use libUsb, but we try to test on all platform
// return new UhkLibUsbApiService(logService);
return new UhkHidApiService(logService);
}

View File

@@ -0,0 +1,106 @@
import { OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Observer } from 'rxjs/Observer';
import { Subscriber } from 'rxjs/Subscriber';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';
import { Constants, LogService } from 'uhk-common';
import { SenderMessage } from '../models/sender-message';
enum Command {
UploadConfig = 8,
ApplyConfig = 9
}
export abstract class UhkDeviceService implements OnDestroy {
protected connected$: BehaviorSubject<boolean>;
protected initialized$: BehaviorSubject<boolean>;
protected deviceOpened$: BehaviorSubject<boolean>;
protected outSubscription: Subscription;
protected messageIn$: Observable<Buffer>;
protected messageOut$: Subject<SenderMessage>;
constructor(protected logService: LogService) {
this.messageOut$ = new Subject<SenderMessage>();
this.initialized$ = new BehaviorSubject(false);
this.connected$ = new BehaviorSubject(false);
this.deviceOpened$ = new BehaviorSubject(false);
this.outSubscription = Subscription.EMPTY;
}
ngOnDestroy() {
this.disconnect();
this.initialized$.unsubscribe();
this.connected$.unsubscribe();
this.deviceOpened$.unsubscribe();
}
sendConfig(configBuffer: Buffer): Observable<Buffer> {
return Observable.create((subscriber: Subscriber<Buffer>) => {
this.logService.info('Sending...', configBuffer);
const fragments: Buffer[] = [];
const MAX_SENDING_PAYLOAD_SIZE = Constants.MAX_PAYLOAD_SIZE - 4;
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
? MAX_SENDING_PAYLOAD_SIZE
: configBuffer.length - offset;
const header = new Buffer([Command.UploadConfig, length, offset & 0xFF, offset >> 8]);
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
}
const buffers: Buffer[] = [];
const observer: Observer<Buffer> = {
next: (buffer: Buffer) => buffers.push(buffer),
error: error => subscriber.error(error),
complete: () => {
if (buffers.length === fragments.length) {
subscriber.next(Buffer.concat(buffers));
subscriber.complete();
this.logService.info('Sending finished');
}
}
};
fragments
.map<SenderMessage>(fragment => ({ buffer: fragment, observer }))
.forEach(senderPackage => this.messageOut$.next(senderPackage));
});
}
applyConfig(): Observable<Buffer> {
return Observable.create((subscriber: Subscriber<Buffer>) => {
this.logService.info('Applying configuration');
this.messageOut$.next({
buffer: new Buffer([Command.ApplyConfig]),
observer: subscriber
});
});
}
isInitialized(): Observable<boolean> {
return this.initialized$.asObservable();
}
isConnected(): Observable<boolean> {
return this.connected$.asObservable();
}
isOpened(): Observable<boolean> {
return this.deviceOpened$.asObservable();
}
disconnect() {
this.outSubscription.unsubscribe();
this.messageIn$ = undefined;
this.initialized$.next(false);
this.deviceOpened$.next(false);
this.connected$.next(false);
}
abstract initialize(): void;
abstract hasPermissions(): Observable<boolean>;
}

View File

@@ -0,0 +1,136 @@
import { Injectable, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subscriber } from 'rxjs/Subscriber';
import { Device, devices, HID } from 'node-hid';
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/concat';
import 'rxjs/add/operator/combineLatest';
import 'rxjs/add/operator/concatMap';
import 'rxjs/add/operator/publish';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/map';
import { Constants, LogService } from 'uhk-common';
import { UhkDeviceService } from './uhk-device.service';
@Injectable()
export class UhkHidApiService extends UhkDeviceService implements OnDestroy {
private device: HID;
constructor(protected logService: LogService) {
super(logService);
}
ngOnDestroy() {
super.ngOnDestroy();
}
initialize(): void {
if (this.initialized$.getValue()) {
return;
}
this.device = this.getDevice();
if (!this.device) {
return;
}
this.deviceOpened$.next(true);
this.messageIn$ = Observable.create((subscriber: Subscriber<Buffer>) => {
this.logService.info('Try to read');
this.device.read((error: any, data: any = []) => {
if (error) {
this.logService.error('reading error', error);
subscriber.error(error);
} else {
this.logService.info('read data', data);
subscriber.next(data);
subscriber.complete();
}
});
});
const outSending = this.messageOut$.concatMap(senderPackage => {
return (<Observable<void>>Observable.create((subscriber: Subscriber<void>) => {
this.logService.info('transfering', senderPackage.buffer);
const data = Array.prototype.slice.call(senderPackage.buffer, 0);
// if data start with 0 need to add additional leading zero because HID API remove it.
// https://github.com/node-hid/node-hid/issues/187
if (data.length > 0 && data[0] === 0) {
data.unshift(0);
}
// From HID API documentation:
// http://www.signal11.us/oss/hidapi/hidapi/doxygen/html/group__API.html#gad14ea48e440cf5066df87cc6488493af
// The first byte of data[] must contain the Report ID.
// For devices which only support a single report, this must be set to 0x0.
data.unshift(0);
try {
this.device.write(data);
this.logService.info('transfering finished');
subscriber.complete();
}
catch (error) {
this.logService.error('transfering errored', error);
subscriber.error(error);
}
})).concat(this.messageIn$)
.do(buffer => senderPackage.observer.next(buffer) && senderPackage.observer.complete())
.catch((error: string) => {
senderPackage.observer.error(error);
return Observable.empty<void>();
});
}).publish();
this.outSubscription = outSending.connect();
this.initialized$.next(true);
}
hasPermissions(): Observable<boolean> {
return this.isConnected()
.combineLatest(this.deviceOpened$)
.map((latest: boolean[]) => {
const connected = latest[0];
const opened = latest[1];
if (!connected) {
return false;
} else if (opened) {
return true;
}
return true;
});
}
/**
* Return the 0 interface of the keyboard.
* @returns {HID}
*/
getDevice(): HID {
try {
const devs = devices();
this.logService.info('Available devices:', devs);
const dev = devs.find((x: Device) =>
x.vendorId === Constants.VENDOR_ID &&
x.productId === Constants.PRODUCT_ID &&
((x.usagePage === 128 && x.usage === 129) || x.interface === 0));
const device = new HID(dev.path);
this.logService.info('Used device:', dev);
return device;
}
catch (err) {
this.logService.error('Can not create device:', err);
}
return null;
}
}