Commit 05e831bd authored by wudizhanche1000's avatar wudizhanche1000

修改 TODO内容

parent a833af7d
...@@ -11,7 +11,7 @@ import "rxjs/Rx"; ...@@ -11,7 +11,7 @@ import "rxjs/Rx";
import * as readline from "readline"; import * as readline from "readline";
import {AppLocal} from "./app-local"; import {AppLocal} from "./app-local";
import * as ini from "ini"; import * as ini from "ini";
import {DownloadService} from "./download.service"; import {DownloadService, DownloadStatus} from "./download.service";
import {InstallOption} from "./install-option"; import {InstallOption} from "./install-option";
import {ComparableSet} from "./shared/ComparableSet"; import {ComparableSet} from "./shared/ComparableSet";
import {Observable, Observer} from "rxjs/Rx"; import {Observable, Observer} from "rxjs/Rx";
...@@ -21,7 +21,14 @@ import ReadableStream = NodeJS.ReadableStream; ...@@ -21,7 +21,14 @@ import ReadableStream = NodeJS.ReadableStream;
const Aria2 = require('aria2'); const Aria2 = require('aria2');
const sudo = require('electron-sudo'); const sudo = require('electron-sudo');
const Logger = {
info: (...message: any[]) => {
console.log("AppService [INFO]: ", ...message);
},
error: (...message: any[]) => {
console.error("AppService [ERROR]: ", ...message);
}
};
interface InstallTask { interface InstallTask {
app: App; app: App;
option: InstallOption; option: InstallOption;
...@@ -101,7 +108,6 @@ export class AppsService { ...@@ -101,7 +108,6 @@ export class AppsService {
} }
// 设置App关系 // 设置App关系
//TODO: 这里有必要重新整理一下么?
for (let [id] of apps) { for (let [id] of apps) {
let temp = apps.get(id)!.actions; let temp = apps.get(id)!.actions;
let map = new Map<string,any>(); let map = new Map<string,any>();
...@@ -153,46 +159,26 @@ export class AppsService { ...@@ -153,46 +159,26 @@ export class AppsService {
} }
await this.doInstall(task); await this.doInstall(task);
}; };
// TODO: 重构这个函数 const addDownloadTask = async(app: App, dir: string): Promise<{app: App,files: string[]} > => {
const addDownloadTask = async(app: App, dir: string) => {
let metalinkUrl = app.download; let metalinkUrl = app.download;
if (app.id === "ygopro") { if (app.id === "ygopro") {
// TODO: 需要特化平台下载的还有个desmume
metalinkUrl = "https://thief.mycard.moe/metalinks/ygopro-" + process.platform + ".meta4"; metalinkUrl = "https://thief.mycard.moe/metalinks/ygopro-" + process.platform + ".meta4";
} }
let metalink = await this.http.get(metalinkUrl).map((response) => response.text()).toPromise(); if (app.id === "desmume") {
metalinkUrl = "https://thief.mycard.moe/metalinks/desmume-" + process.platform + ".meta4";
}
app.status.status = "downloading"; app.status.status = "downloading";
let metalink = await this.http.get(metalinkUrl).map((response) => response.text()).toPromise();
let downloadId = await this.downloadService.addMetalink(metalink, dir); let downloadId = await this.downloadService.addMetalink(metalink, dir);
let observable = this.downloadService.downloadProgress(downloadId); await this.downloadService.progress(downloadId, (status: DownloadStatus) => {
return new Promise((resolve, reject) => { app.status.progress = status.completedLength;
observable.subscribe((task) => { app.status.total = status.totalLength;
if (task.totalLength) { app.status.progressMessage = status.downloadSpeedText;
app.status.total = task.totalLength; this.ref.tick();
} else {
app.status.total = 0;
}
app.status.progress = task.completedLength;
console.log(task);
if (task.downloadSpeed) {
let currentSpeed = parseInt(task.downloadSpeed);
const speedUnit = ["Byte/s", "KB/s", "MB/s", "GB/s", "TB/s"];
let currentUnit = Math.floor(Math.log(currentSpeed) / Math.log(1024));
console.log(currentSpeed, currentUnit);
app.status.progressMessage = (currentSpeed / 1024 ** currentUnit).toFixed(1) + " " + speedUnit[currentUnit];
} else {
app.status.progressMessage = '';
}
this.ref.tick();
}, (error) => {
reject(error);
}, async() => {
app.status.status = "waiting";
let files = await this.downloadService.getFile(downloadId);
resolve({app: app, files: files});
})
}); });
let files = await this.downloadService.getFiles(downloadId);
app.status.status = "waiting";
return {app: app, files: files}
}; };
if (!app.isInstalled()) { if (!app.isInstalled()) {
let apps: App[] = []; let apps: App[] = [];
...@@ -203,16 +189,16 @@ export class AppsService { ...@@ -203,16 +189,16 @@ export class AppsService {
try { try {
let downloadPath = path.join(option.installLibrary, 'downloading'); let downloadPath = path.join(option.installLibrary, 'downloading');
let tasks: Promise<any>[] = []; let tasks: Promise<any>[] = [];
Logger.info("Start to Download", apps);
for (let a of apps) { for (let a of apps) {
tasks.push(addDownloadTask(a, downloadPath)); tasks.push(addDownloadTask(a, downloadPath));
} }
let downloadResults = await Promise.all(tasks); let downloadResults = await Promise.all(tasks);
Logger.info("Download Complete", downloadResults);
let installTasks: Promise<void>[] = []; let installTasks: Promise<void>[] = [];
for (let result of downloadResults) { for (let result of downloadResults) {
console.log(result);
let o = new InstallOption(result.app, option.installLibrary); let o = new InstallOption(result.app, option.installLibrary);
o.downloadFiles = result.files; o.downloadFiles = result.files;
let task = tryToInstall({app: result.app, option: o}); let task = tryToInstall({app: result.app, option: o});
installTasks.push(task); installTasks.push(task);
} }
...@@ -224,7 +210,6 @@ export class AppsService { ...@@ -224,7 +210,6 @@ export class AppsService {
a.reset() a.reset()
} }
} }
console.log(e);
throw e; throw e;
} }
} }
...@@ -233,7 +218,7 @@ export class AppsService { ...@@ -233,7 +218,7 @@ export class AppsService {
findChildren(app: App): App[] { findChildren(app: App): App[] {
let children: App[] = []; let children: App[] = [];
for (let [id,child] of this.apps) { for (let [id,child] of this.apps) {
if (child.parent === app) { if (child.parent === app || child.dependencies && child.dependencies.has(app.id)) {
children.push(child); children.push(child);
} }
} }
...@@ -347,7 +332,6 @@ export class AppsService { ...@@ -347,7 +332,6 @@ export class AppsService {
remote.getCurrentWindow().minimize(); remote.getCurrentWindow().minimize();
} }
// TODO: 定位到下级文件,即现在的目录之内
// 如果有actions.main.execuate, 定位那个execuate的顶层路径 // 如果有actions.main.execuate, 定位那个execuate的顶层路径
// 如果没有,定位目录里面任意一个顶级文件 // 如果没有,定位目录里面任意一个顶级文件
browse(app: App) { browse(app: App) {
...@@ -357,7 +341,7 @@ export class AppsService { ...@@ -357,7 +341,7 @@ export class AppsService {
if (!err) { if (!err) {
remote.shell.showItemInFolder(appPath + "/."); remote.shell.showItemInFolder(appPath + "/.");
} else { } else {
console.log("Browser Window Error:", appPath, err); Logger.error("Browser Window Error:", appPath, err);
} }
}); });
} }
...@@ -450,7 +434,7 @@ export class AppsService { ...@@ -450,7 +434,7 @@ export class AppsService {
let app = task.app; let app = task.app;
if (!app.isWaiting()) { if (!app.isWaiting()) {
console.error('doUninstall', "应用不处于等待安装状态", JSON.stringify(app)); console.error('doUninstall', "应用不处于等待安装状态", app);
throw("应用不处于等待安装状态"); throw("应用不处于等待安装状态");
} }
...@@ -652,9 +636,8 @@ export class AppsService { ...@@ -652,9 +636,8 @@ export class AppsService {
for (let line of response.text().split('\n')) { for (let line of response.text().split('\n')) {
if (line !== "") { if (line !== "") {
let [checksum,filename]=line.split(' ', 2); let [checksum,filename]=line.split(' ', 2);
if (filename.endsWith("\\") || filename.endsWith("/")) { // checksum文件里没有文件夹,这里添加上
map.set(filename, ""); map.set(path.dirname(filename), "");
}
map.set(filename, checksum); map.set(filename, checksum);
} }
} }
...@@ -681,16 +664,19 @@ export class AppsService { ...@@ -681,16 +664,19 @@ export class AppsService {
} }
async uninstall(app: App) { async uninstall(app: App) {
// TODO: 级联卸载mod
let children = this.findChildren(app); let children = this.findChildren(app);
let hasInstalledChild = children.find((child) => { let hasInstalledChild = children.find((child) => {
return child.isInstalled(); return child.isInstalled() && child.parent != app;
}); });
if (hasInstalledChild) { if (hasInstalledChild) {
throw "无法卸载,还有依赖此程序的游戏。" throw "无法卸载,还有依赖此程序的游戏。"
} } else if (app.isReady()) {
if (app.isReady()) { for (let child of children) {
return this.doUninstall(app); if (child.parent === app && child.isReady()) {
await this.doUninstall(child);
}
}
return await this.doUninstall(app);
} }
} }
...@@ -723,7 +709,7 @@ export class AppsService { ...@@ -723,7 +709,7 @@ export class AppsService {
let parentSet = new ComparableSet(Array.from(app.parent.local!.files.keys())); let parentSet = new ComparableSet(Array.from(app.parent.local!.files.keys()));
let difference = parentSet.intersection(fileSet); let difference = parentSet.intersection(fileSet);
if (difference) { if (difference) {
this.restoreFiles(appDir, backupDir, Array.from(difference)) await this.restoreFiles(appDir, backupDir, Array.from(difference))
} }
} }
......
...@@ -7,30 +7,103 @@ import {Observable} from "rxjs/Observable"; ...@@ -7,30 +7,103 @@ import {Observable} from "rxjs/Observable";
import {App} from "./app"; import {App} from "./app";
import {Observer} from "rxjs"; import {Observer} from "rxjs";
import Timer = NodeJS.Timer; import Timer = NodeJS.Timer;
import {error} from "util";
const Logger = {
"error": (message: string) => {
console.error("DownloadService: ", message);
}
};
const Aria2 = require('aria2'); const Aria2 = require('aria2');
const MAX_LIST_NUM = 1000; const MAX_LIST_NUM = 1000;
const ARIA2_INTERVAL = 1000; const ARIA2_INTERVAL = 1000;
export interface DownloadStatus { export class DownloadStatus {
completedLength: string; completedLength: number;
downloadSpeed: string; downloadSpeed: number;
get downloadSpeedText(): string {
if (!isNaN(this.downloadSpeed)) {
const speedUnit = ["Byte/s", "KB/s", "MB/s", "GB/s", "TB/s"];
let currentUnit = Math.floor(Math.log(this.downloadSpeed) / Math.log(1024));
return (this.downloadSpeed / 1024 ** currentUnit).toFixed(1) + " " + speedUnit[currentUnit];
}
return "";
};
gid: string; gid: string;
status: string; status: string;
totalLength: string; totalLength: number;
totalLengthText: string;
errorCode: string; errorCode: string;
errorMessage: string; errorMessage: string;
combine(...others: DownloadStatus[]): DownloadStatus {
const priority = {
undefined: -1,
"": -1,
"active": 0,
"complete": 0,
"paused": 1,
"waiting": 1,
"removed": 2,
"error": 3
};
let status = Object.assign(new DownloadStatus(), this);
for (let o of others) {
if (priority[o.status] > priority[status.status]) {
status.status = o.status;
if (status.status === "error") {
status.errorCode = o.errorCode;
status.errorMessage = o.errorMessage;
}
status.downloadSpeed += o.downloadSpeed;
status.totalLength += o.totalLength;
status.completedLength += o.completedLength;
}
}
return status;
}
// 0相等. 1不想等
compareTo(other: DownloadStatus): number {
if (this.status !== other.status ||
this.downloadSpeed !== other.downloadSpeed ||
this.completedLength !== other.completedLength ||
this.totalLength !== other.totalLength) {
return 1;
} else {
return 0;
}
}
constructor(item ?: any) {
if (item) {
this.completedLength = parseInt(item.completedLength) || 0;
this.downloadSpeed = parseInt(item.downloadSpeed) || 0;
this.totalLength = parseInt(item.totalLength) || 0;
this.gid = item.gid;
this.status = item.status;
this.errorCode = item.errorCode;
this.errorMessage = item.errorMessage;
} else {
this.completedLength = 0;
this.downloadSpeed = 0;
this.totalLength = 0;
}
}
} }
@Injectable() @Injectable()
export class DownloadService { export class DownloadService {
aria2 = new Aria2(); aria2 = new Aria2();
open = this.aria2.open(); open = this.aria2.open();
updateEmitter = new EventEmitter<string>(); updateEmitter = new EventEmitter<void>();
progressList: Map<string,(Observable<any>)> = new Map();
taskList: Map<string,DownloadStatus> = new Map(); downloadList: Map<string,DownloadStatus> = new Map();
map: Map<string,string[]> = new Map(); taskMap: Map<string,string[]> = new Map();
constructor(private ngZone: NgZone, private http: Http) { constructor(private ngZone: NgZone, private http: Http) {
ngZone.runOutsideAngular(async() => { ngZone.runOutsideAngular(async() => {
...@@ -40,15 +113,15 @@ export class DownloadService { ...@@ -40,15 +113,15 @@ export class DownloadService {
let waitList = await this.aria2.tellWaiting(0, MAX_LIST_NUM); let waitList = await this.aria2.tellWaiting(0, MAX_LIST_NUM);
let stoppedList = await this.aria2.tellStopped(0, MAX_LIST_NUM); let stoppedList = await this.aria2.tellStopped(0, MAX_LIST_NUM);
for (let item of activeList) { for (let item of activeList) {
this.taskList.set(item.gid, item); this.downloadList.set(item.gid, new DownloadStatus(item));
} }
for (let item of waitList) { for (let item of waitList) {
this.taskList.set(item.gid, item); this.downloadList.set(item.gid, new DownloadStatus(item));
} }
for (let item of stoppedList) { for (let item of stoppedList) {
this.taskList.set(item.gid, item); this.downloadList.set(item.gid, new DownloadStatus(item));
} }
this.updateEmitter.emit("updated"); this.updateEmitter.emit();
}, ARIA2_INTERVAL); }, ARIA2_INTERVAL);
}) })
} }
...@@ -64,72 +137,111 @@ export class DownloadService { ...@@ -64,72 +137,111 @@ export class DownloadService {
s4() + '-' + s4() + s4() + s4(); s4() + '-' + s4() + s4() + s4();
} }
downloadProgress(id: string): Observable<any> { async progress(id: string, callback: (downloadStatus: DownloadStatus)=>void) {
let progress = this.progressList.get(id); return new Promise((resolve, reject) => {
if (progress) { let gids = this.taskMap.get(id);
return progress; if (gids) {
} else { let allStatus: DownloadStatus;
return Observable.create((observer: Observer<any>) => { this.updateEmitter.subscribe(() => {
let status = ''; let status: DownloadStatus = new DownloadStatus();
let completedLength = 0; // 合并每个状态信息
let totalLength = 0; status =
let downloadSpeed = 0; gids!.map((value, index, array) => {
let s = this.downloadList.get(value);
let gidList = this.map.get(id) !; if (!s) {
this.updateEmitter.subscribe((value: string) => { throw new Error("Gid not Exists");
let statusList = new Array(gidList.length); }
let newCompletedLength = 0; return s;
let newTotalLength = 0; })
let newDownloadSpeed = 0; .reduce((previousValue, currentValue, currentIndex, array) => {
for (let [index,gid] of gidList.entries()) { return previousValue.combine(currentValue);
let task = this.taskList.get(gid)!; }, status);
if (task) { if (!allStatus) {
statusList[index] = task.status; allStatus = status;
newCompletedLength += parseInt(task.completedLength); } else {
newTotalLength += parseInt(task.totalLength); if (allStatus.compareTo(status) != 0) {
newDownloadSpeed += parseInt(task.downloadSpeed); allStatus = status;
}
}
if (newCompletedLength !== completedLength || newTotalLength !== totalLength) {
completedLength = newCompletedLength;
totalLength = newTotalLength;
downloadSpeed = newDownloadSpeed;
observer.next({
status: status,
completedLength: completedLength,
totalLength: totalLength,
downloadSpeed: downloadSpeed
});
}
status = statusList.reduce((value, current) => {
if (value === "complete" && current === "complete") {
return "complete";
} }
if (current != "complete" && current != "active") {
return "error";
}
});
if (status === "complete") {
observer.complete();
} else if (status == "error") {
observer.error("Download Error");
} }
return () => { if (allStatus.status === "error") {
reject(`Download Error: code ${allStatus.errorCode}, message: ${allStatus.errorMessage}`);
} else if (allStatus.status === "complete") {
resolve();
} else {
callback(allStatus);
} }
}, () => { });
} else {
}, () => { throw "Try to access invalid download id";
}
}) })
});
}
} }
async getFile(id: string): Promise<string[]> { // downloadProgress(id: string): Observable<any> {
let gids = this.map.get(id)!; // let progress = this.progressList.get(id);
console.log('gids ', gids); // if (progress) {
// return progress;
// } else {
// return Observable.create((observer: Observer<any>) => {
// let status = '';
// let completedLength = 0;
// let totalLength = 0;
// let downloadSpeed = 0;
//
// let gidList = this.taskMap.get(id) !;
// this.updateEmitter.subscribe((value: string) => {
// let statusList = new Array(gidList.length);
// let newCompletedLength = 0;
// let newTotalLength = 0;
// let newDownloadSpeed = 0;
// for (let [index,gid] of gidList.entries()) {
// let task = this.downloadList.get(gid)!;
// if (task) {
// statusList[index] = task.status;
// newCompletedLength += parseInt(task.completedLength);
// newTotalLength += parseInt(task.totalLength);
// newDownloadSpeed += parseInt(task.downloadSpeed);
// }
// }
// if (newCompletedLength !== completedLength || newTotalLength !== totalLength) {
// completedLength = newCompletedLength;
// totalLength = newTotalLength;
// downloadSpeed = newDownloadSpeed;
// observer.next({
// status: status,
// completedLength: completedLength,
// totalLength: totalLength,
// downloadSpeed: downloadSpeed
// });
// }
// status = statusList.reduce((value, current) => {
// if (value === "complete" && current === "complete") {
// return "complete";
// }
// if (current != "complete" && current != "active") {
// return "error";
// }
// });
// if (status === "complete") {
// observer.complete();
// } else if (status == "error") {
// observer.error("Download Error");
// }
// return () => {
//
// }
// }, () => {
//
// }, () => {
//
// })
// });
//
// }
// }
async getFiles(id: string): Promise<string[]> {
let gids = this.taskMap.get(id)!;
let files: string[] = []; let files: string[] = [];
for (let gid of gids) { for (let gid of gids) {
let file = await this.aria2.getFiles(gid); let file = await this.aria2.getFiles(gid);
...@@ -142,14 +254,16 @@ export class DownloadService { ...@@ -142,14 +254,16 @@ export class DownloadService {
let encodedMeta4 = new Buffer((metalink)).toString('base64'); let encodedMeta4 = new Buffer((metalink)).toString('base64');
let gidList = await this.aria2.addMetalink(encodedMeta4, {dir: library}); let gidList = await this.aria2.addMetalink(encodedMeta4, {dir: library});
let taskId = this.createId(); let taskId = this.createId();
this.map.set(taskId, gidList); this.taskMap.set(taskId, gidList);
return taskId; return taskId;
} }
async addUri(url: string, destination: string): Promise<string> { async addUri(url: string, destination: string): Promise<string> {
await this.open; await this.open;
let id = await this.aria2.addUri([url], {dir: destination}); let taskId = this.createId();
return id; let gid = await this.aria2.addUri([url], {dir: destination});
this.taskMap.set(taskId, [gid]);
return taskId;
} }
async pause(id: string): Promise<void> { async pause(id: string): Promise<void> {
......
...@@ -16,7 +16,7 @@ export class SettingsService { ...@@ -16,7 +16,7 @@ export class SettingsService {
static defaultLibraries = [ static defaultLibraries = [
{ {
"default": true, "default": true,
path: path.join(remote.app.getPath("appData"), "library") path: path.join(remote.app.getPath("appData"), "MyCardLibrary")
}, },
]; ];
libraries: Library[]; libraries: Library[];
......
...@@ -180,7 +180,7 @@ function createTray() { ...@@ -180,7 +180,7 @@ function createTray() {
app.on('ready', () => { app.on('ready', () => {
createWindow(); createWindow();
if (process.env['NODE_ENV'] == 'production') { if (process.env['NODE_ENV'] == 'production') {
setTimeout(autoUpdater.checkForUpdates, 2000); setTimeout(autoUpdater.checkForUpdates.bind(autoUpdater), 2000);
} }
if (process.platform == 'win32') { if (process.platform == 'win32') {
createTray() createTray()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment