file download is implemented
This commit is contained in:
parent
958295a8c1
commit
88e715eb60
|
@ -2,12 +2,15 @@ import { app, ipcMain, IpcMainEvent, remote } from 'electron';
|
|||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import * as fse from 'fs-extra';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { AppWindow } from './app/AppWindow';
|
||||
import { now } from './util/now';
|
||||
import { showUncaughtException } from './crash/show-uncaught-exception';
|
||||
import { Channel } from '@ucap-webmessenger/native-electron';
|
||||
import { root } from './util/root';
|
||||
import { DefaultFolder } from './lib/default-folder';
|
||||
import { FileUtil } from './lib/file-util';
|
||||
|
||||
let appWindow: AppWindow | null = null;
|
||||
|
||||
|
@ -179,19 +182,37 @@ ipcMain.on(Channel.checkForUpdates, (event: IpcMainEvent, ...args: any[]) => {
|
|||
|
||||
ipcMain.on(Channel.readFile, (event: IpcMainEvent, ...args: any[]) => {
|
||||
try {
|
||||
const rBuf = fse.readFileSync(root(args[0]));
|
||||
event.returnValue = rBuf.buffer;
|
||||
fse.readFile(root(args[0]), (err, data) => {
|
||||
if (!!err) {
|
||||
event.returnValue = null;
|
||||
} else {
|
||||
event.returnValue = new Blob([data]);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
event.returnValue = null;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on(Channel.saveFile, (event: IpcMainEvent, ...args: any[]) => {
|
||||
ipcMain.on(Channel.saveFile, async (event: IpcMainEvent, ...args: any[]) => {
|
||||
try {
|
||||
fse.writeFileSync(root(args[0]), args[1]);
|
||||
event.returnValue = true;
|
||||
const buffer: Buffer = args[0];
|
||||
const fileName: string = args[1];
|
||||
let savePath: string = path.join(
|
||||
!!args[2] ? args[2] : DefaultFolder.downloads(),
|
||||
fileName
|
||||
);
|
||||
savePath = await FileUtil.uniqueFileName(savePath);
|
||||
|
||||
fse.writeFile(savePath, buffer, err => {
|
||||
if (!err) {
|
||||
event.returnValue = savePath;
|
||||
} else {
|
||||
event.returnValue = undefined;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
event.returnValue = false;
|
||||
event.returnValue = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
37
main/src/lib/default-folder.ts
Normal file
37
main/src/lib/default-folder.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import * as os from 'os';
|
||||
import { execSync } from 'child_process';
|
||||
import * as fse from 'fs-extra';
|
||||
|
||||
export class DefaultFolder {
|
||||
static downloads(): string {
|
||||
switch (os.platform()) {
|
||||
case 'win32':
|
||||
return `${process.env.USERPROFILE}/Downloads`;
|
||||
case 'darwin':
|
||||
return `${process.env.HOME}/Downloads`;
|
||||
case 'linux': {
|
||||
let dir: Buffer;
|
||||
try {
|
||||
dir = execSync('xdg-user-dir DOWNLOAD', { stdio: [0, 3, 3] });
|
||||
} catch (_) {}
|
||||
|
||||
if (dir) {
|
||||
return dir.toString('utf-8');
|
||||
}
|
||||
|
||||
let stat: fse.Stats;
|
||||
const homeDownloads = `${process.env.HOME}/Downloads`;
|
||||
try {
|
||||
stat = fse.statSync(homeDownloads);
|
||||
} catch (_) {}
|
||||
if (stat) {
|
||||
return homeDownloads;
|
||||
}
|
||||
return '/tmp/';
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
222
main/src/lib/file-util.ts
Normal file
222
main/src/lib/file-util.ts
Normal file
|
@ -0,0 +1,222 @@
|
|||
import * as path from 'path';
|
||||
import * as fse from 'fs-extra';
|
||||
|
||||
/**
|
||||
*
|
||||
* separator
|
||||
* If the specified filename exists, the separator will be added before the incremental value such as: file{separator}2.jpg
|
||||
* The default value is '-'.
|
||||
*
|
||||
* mode
|
||||
* The mode allows you to specify which characters to use to generate the incremental value (the string after the separator)
|
||||
* The default value is 'numeric'.
|
||||
* 'numeric' Using the following characters: 1234567890
|
||||
* 'alpha' Using the following characters: abcdefghijklmnopqrstuvwxyz
|
||||
* 'ALPHA' Using the following characters: ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
* 'alphanumeric' Using the following characters: 0123456789abcdefghijklmnopqrstuvwxyz
|
||||
* 'ALPHANUMERIC' Using the following characters: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
* 'charset' You must specify the characters you wish to use in the charset option
|
||||
*
|
||||
* paddingCharacter && paddingSize
|
||||
* If you wish to left-pad the incremental values with a character, use this option. Here's an example :
|
||||
* var uniquefilename = require('uniquefilename');
|
||||
* options = {mode: 'alpha', paddingCharacter: '0', paddingSize: 3};
|
||||
* uniquefilename.get('/path/to/dir/file.jpg', options, function(filename) {
|
||||
* // filename might be "/path/to/dir/file.jpg",
|
||||
* // "/path/to/dir/file-002.jpg", "/path/to/dir/file-045.jpg", etc...
|
||||
* // depending on the files that exist on your filesystem
|
||||
* });
|
||||
*
|
||||
* alwaysAppend
|
||||
* If alwaysAppend is true filenames will include the separator and attachment from the first request.
|
||||
* So instead of file.jpg, file-2.jpg you'd get file-1.jpg, file-2.jpg.
|
||||
*/
|
||||
export interface UniqueFileNameOption {
|
||||
separator?: string;
|
||||
mode?:
|
||||
| 'numeric'
|
||||
| 'alpha'
|
||||
| 'ALPHA'
|
||||
| 'alphanumeric'
|
||||
| 'ALPHANUMERIC'
|
||||
| 'charset';
|
||||
paddingCharacter?: string;
|
||||
paddingSize?: number;
|
||||
alwaysAppend?: boolean;
|
||||
charset?: string;
|
||||
}
|
||||
|
||||
const charsets = {
|
||||
alpha: 'abcdefghijklmnopqrstuvwxyz',
|
||||
alphanumeric: '0123456789abcdefghijklmnopqrstuvwxyz',
|
||||
ALPHA: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
ALPHANUMERIC: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
};
|
||||
|
||||
interface UniqueFile {
|
||||
dir?: string;
|
||||
ext?: string;
|
||||
base?: string;
|
||||
increment?: number;
|
||||
}
|
||||
|
||||
export class FileUtil {
|
||||
static blobToBuffer(blob: Blob): Promise<Buffer> {
|
||||
if (typeof Blob === 'undefined' || !(blob instanceof Blob)) {
|
||||
throw new Error('first argument must be a Blob');
|
||||
}
|
||||
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
resolve(Buffer.from(reader.result as ArrayBuffer));
|
||||
};
|
||||
reader.onerror = () => {
|
||||
reader.abort();
|
||||
reject(reader.error);
|
||||
};
|
||||
reader.readAsArrayBuffer(blob);
|
||||
});
|
||||
}
|
||||
|
||||
static uniqueFileName(
|
||||
filePath: string,
|
||||
options?: UniqueFileNameOption
|
||||
): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const dir = path.dirname(filePath);
|
||||
const ext = path.extname(filePath);
|
||||
const base = path.basename(filePath, ext);
|
||||
|
||||
const uniqueFile: UniqueFile = {
|
||||
dir,
|
||||
ext,
|
||||
base
|
||||
};
|
||||
|
||||
options = options || {};
|
||||
options.separator = options.separator || '-';
|
||||
options.mode = options.mode || 'numeric';
|
||||
|
||||
if ('numeric' !== options.mode) {
|
||||
if (charsets[options.mode]) {
|
||||
options.charset = charsets[options.mode];
|
||||
options.mode = 'charset';
|
||||
} else if (
|
||||
'charset' !== options.mode ||
|
||||
('charset' === options.mode && !options.charset)
|
||||
) {
|
||||
options.mode = 'numeric';
|
||||
}
|
||||
}
|
||||
|
||||
if (options.paddingSize && !options.paddingCharacter) {
|
||||
options.paddingCharacter = '0';
|
||||
}
|
||||
|
||||
FileUtil.uniqueFileNameProcess(
|
||||
uniqueFile,
|
||||
options,
|
||||
(fileName: string) => {
|
||||
resolve(fileName);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private static uniqueFileNameProcess(
|
||||
uniqueFile: UniqueFile,
|
||||
options: UniqueFileNameOption,
|
||||
callback: (fileName: string) => void
|
||||
) {
|
||||
let fileName: string;
|
||||
let append = '';
|
||||
|
||||
if (options.alwaysAppend && !uniqueFile.increment) {
|
||||
uniqueFile.increment = 1;
|
||||
}
|
||||
|
||||
if (uniqueFile.increment) {
|
||||
if ('numeric' === options.mode) {
|
||||
append = '' + uniqueFile.increment;
|
||||
} else {
|
||||
append = FileUtil.numberToString(uniqueFile.increment, options.charset);
|
||||
}
|
||||
|
||||
if (options.paddingSize) {
|
||||
while (append.length < options.paddingSize) {
|
||||
append = options.paddingCharacter + append;
|
||||
}
|
||||
}
|
||||
|
||||
append = options.separator + append;
|
||||
}
|
||||
|
||||
fileName = path.join(
|
||||
uniqueFile.dir,
|
||||
uniqueFile.base + append + uniqueFile.ext
|
||||
);
|
||||
if (fse.existsSync(fileName)) {
|
||||
if (uniqueFile.increment) {
|
||||
uniqueFile.increment += 1;
|
||||
} else {
|
||||
uniqueFile.increment = 'numeric' === options.mode ? 2 : 1;
|
||||
}
|
||||
return FileUtil.uniqueFileNameProcess(uniqueFile, options, callback);
|
||||
} else {
|
||||
return callback(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
private static numberToString(nbr: number, charset: string) {
|
||||
const charsetLen = charset.length;
|
||||
let strLen = 0;
|
||||
let strThisLen = 0;
|
||||
let tmp: number;
|
||||
|
||||
for (let maxpower = 20; maxpower >= 0; maxpower--) {
|
||||
const maxvalue = FileUtil.sumOfPowerFromOne(charsetLen, maxpower);
|
||||
|
||||
if (maxvalue < nbr) {
|
||||
strLen = maxpower + 1;
|
||||
strThisLen = maxvalue + Math.pow(charsetLen, maxpower + 1) - maxvalue;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === strLen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let str = '';
|
||||
while (--strLen >= 0) {
|
||||
if (strLen === 0) {
|
||||
str += charset.charAt(nbr - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
strThisLen = Math.pow(charsetLen, strLen);
|
||||
const initial = FileUtil.sumOfPowerFromOne(charsetLen, strLen - 1);
|
||||
|
||||
for (tmp = charsetLen; tmp >= 1; tmp--) {
|
||||
if (initial + tmp * strThisLen < nbr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nbr -= tmp * strThisLen;
|
||||
str += charset.charAt(tmp - 1);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
private static sumOfPowerFromOne(base: number, maxpower: number) {
|
||||
let value = 0;
|
||||
for (let tmp = maxpower; tmp >= 1; tmp--) {
|
||||
value += Math.pow(base, tmp);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -9,8 +9,10 @@ import {
|
|||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { FileEventJson } from '@ucap-webmessenger/protocol-event';
|
||||
import { DeviceType } from '@ucap-webmessenger/core';
|
||||
import * as FileSaver from 'file-saver';
|
||||
import { DeviceType, FileUtil } from '@ucap-webmessenger/core';
|
||||
import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { SnackBarService } from '@ucap-webmessenger/ui';
|
||||
|
||||
export interface FileViewerDialogData {
|
||||
fileInfo: FileEventJson;
|
||||
|
@ -40,6 +42,8 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
|
|||
FileViewerDialogResult
|
||||
>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: FileViewerDialogData,
|
||||
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
|
||||
private snackBarService: SnackBarService,
|
||||
private logger: NGXLogger
|
||||
) {
|
||||
this.fileInfo = data.fileInfo;
|
||||
|
@ -54,7 +58,29 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
|
|||
ngOnDestroy(): void {}
|
||||
|
||||
onDownload(blob: Blob): void {
|
||||
FileSaver.saveAs(blob, this.fileInfo.fileName);
|
||||
FileUtil.fromBlobToBuffer(blob)
|
||||
.then(buffer => {
|
||||
this.nativeService
|
||||
.saveFile(buffer, this.fileInfo.fileName)
|
||||
.pipe(take(1))
|
||||
.subscribe(result => {
|
||||
if (!!result) {
|
||||
this.snackBarService.open(
|
||||
`파일이 경로[${result}]에 저장되었습니다.`,
|
||||
'',
|
||||
{
|
||||
duration: 3000,
|
||||
verticalPosition: 'bottom'
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.snackBarService.open('파일 저장에 실패하였습니다.');
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(reason => {
|
||||
this.logger.error('download', reason);
|
||||
});
|
||||
}
|
||||
|
||||
onClosedViewer(): void {
|
||||
|
|
|
@ -14,11 +14,9 @@ export class FileUtil {
|
|||
return extensions;
|
||||
}
|
||||
|
||||
static toDataUrl(blob: Blob): Promise<string | ArrayBuffer> {
|
||||
static fromBlobToDataUrl(blob: Blob): Promise<string | ArrayBuffer> {
|
||||
return new Promise<string | ArrayBuffer>((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.readAsDataURL(blob);
|
||||
|
||||
fileReader.onload = () => {
|
||||
return resolve(fileReader.result);
|
||||
};
|
||||
|
@ -26,6 +24,35 @@ export class FileUtil {
|
|||
fileReader.abort();
|
||||
return reject(fileReader.error);
|
||||
};
|
||||
fileReader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
static fromBlobToBuffer(blob: Blob): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = () => {
|
||||
return resolve(Buffer.from(fileReader.result as ArrayBuffer));
|
||||
};
|
||||
fileReader.onerror = (event: ProgressEvent) => {
|
||||
fileReader.abort();
|
||||
return reject(fileReader.error);
|
||||
};
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
});
|
||||
}
|
||||
|
||||
static fromBlobToString(blob: Blob): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = () => {
|
||||
return resolve(fileReader.result.toString());
|
||||
};
|
||||
fileReader.onerror = (event: ProgressEvent) => {
|
||||
fileReader.abort();
|
||||
return reject(fileReader.error);
|
||||
};
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,12 +25,20 @@ export class BrowserNativeService implements NativeService {
|
|||
|
||||
showImageViewer(): void {}
|
||||
|
||||
readFile(path: string): Observable<ArrayBuffer> {
|
||||
return this.httpClient.get(path, { responseType: 'arraybuffer' });
|
||||
readFile(path: string): Observable<Buffer> {
|
||||
return this.httpClient.get(path, { responseType: 'arraybuffer' }).pipe(
|
||||
map(arrayBuffer => {
|
||||
return Buffer.from(arrayBuffer);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
saveFile(path: string, buf: ArrayBuffer): Observable<boolean> {
|
||||
return this.httpClient.post<boolean>(path, buf, {});
|
||||
saveFile(
|
||||
buffer: Buffer,
|
||||
fileName: string,
|
||||
path?: string
|
||||
): Observable<string> {
|
||||
return this.httpClient.post<string>(path, buffer, {});
|
||||
}
|
||||
|
||||
windowStateChanged(): Observable<WindowState> {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { TranslateLoader } from '@ngx-translate/core';
|
|||
import { Observable } from 'rxjs';
|
||||
import { NativeService } from '@ucap-webmessenger/native';
|
||||
import { take, map } from 'rxjs/operators';
|
||||
import { FileUtil } from '@ucap-webmessenger/core';
|
||||
|
||||
export class TranslateBrowserLoader implements TranslateLoader {
|
||||
constructor(
|
||||
|
@ -18,8 +19,8 @@ export class TranslateBrowserLoader implements TranslateLoader {
|
|||
.readFile(`${this.prefix}${lang}.${this.suffix}`)
|
||||
.pipe(
|
||||
take(1),
|
||||
map(buf => {
|
||||
return JSON.parse(Buffer.from(buf).toString('utf8'));
|
||||
map(buffer => {
|
||||
return JSON.parse(buffer.toString('utf-8'));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -42,8 +42,8 @@ export class ElectronNativeService implements NativeService {
|
|||
ipcRenderer.send(Channel.showImageViewer);
|
||||
}
|
||||
|
||||
readFile(path: string): Observable<ArrayBuffer> {
|
||||
return new Observable<ArrayBuffer>(subscriber => {
|
||||
readFile(path: string): Observable<Buffer> {
|
||||
return new Observable<Buffer>(subscriber => {
|
||||
try {
|
||||
subscriber.next(ipcRenderer.sendSync(Channel.readFile, path));
|
||||
} catch (error) {
|
||||
|
@ -54,10 +54,16 @@ export class ElectronNativeService implements NativeService {
|
|||
});
|
||||
}
|
||||
|
||||
saveFile(path: string, buf: ArrayBuffer): Observable<boolean> {
|
||||
return new Observable<boolean>(subscriber => {
|
||||
saveFile(
|
||||
buffer: Buffer,
|
||||
fileName: string,
|
||||
path?: string
|
||||
): Observable<string> {
|
||||
return new Observable<string>(subscriber => {
|
||||
try {
|
||||
subscriber.next(ipcRenderer.sendSync(Channel.saveFile, path, buf));
|
||||
subscriber.next(
|
||||
ipcRenderer.sendSync(Channel.saveFile, buffer, fileName, path)
|
||||
);
|
||||
} catch (error) {
|
||||
subscriber.error(error);
|
||||
} finally {
|
||||
|
|
|
@ -9,8 +9,8 @@ export interface NativeService {
|
|||
|
||||
showImageViewer(): void;
|
||||
|
||||
saveFile(path: string, buf: ArrayBuffer): Observable<boolean>;
|
||||
readFile(path: string): Observable<ArrayBuffer>;
|
||||
saveFile(buffer: Buffer, fileName: string, path?: string): Observable<string>;
|
||||
readFile(path: string): Observable<Buffer>;
|
||||
|
||||
windowStateChanged(): Observable<WindowState>;
|
||||
windowClose(): void;
|
||||
|
|
|
@ -69,7 +69,7 @@ export class ImageViewerComponent implements OnInit {
|
|||
blob.size,
|
||||
MimeUtil.getMimeFromExtension(this.fileInfo.fileExt)
|
||||
);
|
||||
this.imageSrc = await FileUtil.toDataUrl(this.blob);
|
||||
this.imageSrc = await FileUtil.fromBlobToDataUrl(this.blob);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
|
|
Loading…
Reference in New Issue
Block a user