Commit 214b912b authored by 神楽坂玲奈's avatar 神楽坂玲奈

apps service 一些改动,未测试

parent b2bf569f
...@@ -7,7 +7,6 @@ import {DownloadService} from "./download.service"; ...@@ -7,7 +7,6 @@ import {DownloadService} from "./download.service";
import {clipboard, remote} from "electron"; import {clipboard, remote} from "electron";
import * as path from "path"; import * as path from "path";
import * as fs from 'fs'; import * as fs from 'fs';
import mkdirp = require("mkdirp");
declare const Notification: any; declare const Notification: any;
declare const $: any; declare const $: any;
...@@ -32,30 +31,10 @@ export class AppDetailComponent implements OnInit { ...@@ -32,30 +31,10 @@ export class AppDetailComponent implements OnInit {
private downloadService: DownloadService, private ref: ChangeDetectorRef) { private downloadService: DownloadService, private ref: ChangeDetectorRef) {
} }
// public File[] listRoots() { async ngOnInit() {
// int ds = listRoots0();
// int n = 0;
// for (int i = 0; i < 26; i++) {
// if (((ds >> i) & 1) != 0) {
// if (!access((char)('A' + i) + ":" + slash))
// ds &= ~(1 << i);
// else
// n++;
// }
// }
// File[] fs = new File[n];
// int j = 0;
// char slash = this.slash;
// for (int i = 0; i < 26; i++) {
// if (((ds >> i) & 1) != 0)
// fs[j++] = new File((char)('A' + i) + ":" + slash);
// }
// return fs;
// }
ngOnInit() {
let volume = 'A'; let volume = 'A';
for (let i = 0; i < 26; i++) { for (let i = 0; i < 26; i++) {
new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
let currentVolume = String.fromCharCode(volume.charCodeAt(0) + i) + ":"; let currentVolume = String.fromCharCode(volume.charCodeAt(0) + i) + ":";
fs.access(currentVolume, (err) => { fs.access(currentVolume, (err) => {
if (!err) { if (!err) {
...@@ -64,6 +43,7 @@ export class AppDetailComponent implements OnInit { ...@@ -64,6 +43,7 @@ export class AppDetailComponent implements OnInit {
this.availableLibraries.push(currentVolume); this.availableLibraries.push(currentVolume);
} }
} }
resolve()
}) })
}) })
} }
...@@ -103,7 +83,6 @@ export class AppDetailComponent implements OnInit { ...@@ -103,7 +83,6 @@ export class AppDetailComponent implements OnInit {
if (confirm("确认删除?")) { if (confirm("确认删除?")) {
try { try {
await this.appsService.uninstall(app); await this.appsService.uninstall(app);
app.status.status = "init";
} catch (e) { } catch (e) {
alert(e); alert(e);
} }
......
...@@ -81,6 +81,11 @@ export class App { ...@@ -81,6 +81,11 @@ export class App {
status: AppStatus; status: AppStatus;
conference: string | undefined; conference: string | undefined;
reset() {
this.status.status = 'init';
this.local = null;
localStorage.removeItem(this.id);
}
isInstalled(): boolean { isInstalled(): boolean {
return this.status.status != 'init'; return this.status.status != 'init';
} }
......
...@@ -16,7 +16,7 @@ import {InstallOption} from "./install-option"; ...@@ -16,7 +16,7 @@ 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";
import Timer = NodeJS.Timer; import Timer = NodeJS.Timer;
import mkdirp = require("mkdirp"); // import mkdirp = require("mkdirp");
import ReadableStream = NodeJS.ReadableStream; import ReadableStream = NodeJS.ReadableStream;
const Aria2 = require('aria2'); const Aria2 = require('aria2');
...@@ -41,18 +41,10 @@ export class AppsService { ...@@ -41,18 +41,10 @@ export class AppsService {
private apps: Map<string,App>; private apps: Map<string,App>;
readonly tarPath = process.platform === "win32" ? path.join(process.env['NODE_ENV'] == 'production' ? process.resourcesPath : '', 'bin', 'bsdtar.exe') : 'bsdtar';
constructor(private http: Http, private settingsService: SettingsService, private ref: ApplicationRef, constructor(private http: Http, private settingsService: SettingsService, private ref: ApplicationRef,
private downloadService: DownloadService) { private downloadService: DownloadService) {
if (process.platform === "win32") {
if (process.env['NODE_ENV'] == 'production') {
this.tarPath = path.join(process.resourcesPath, 'bin', 'bsdtar.exe');
} else {
this.tarPath = path.join('bin', 'bsdtar.exe');
}
} else {
this.tarPath = "bsdtar"
}
} }
loadApps() { loadApps() {
...@@ -81,20 +73,20 @@ export class AppsService { ...@@ -81,20 +73,20 @@ export class AppsService {
if (local) { if (local) {
app.status.status = "ready"; app.status.status = "ready";
} else { } else {
app.status.status = "init"; app.reset()
} }
// 去除无关语言 // 去除无关语言
['name', 'description'].forEach((key) => { for (let key of ['name', 'description']) {
let value = app[key][locale]; let value = app[key][locale];
if (!value) { if (!value) {
value = app[key]["zh-CN"]; value = app[key]["zh-CN"];
} }
app[key] = value; app[key] = value;
}); }
// 去除平台无关的内容 // 去除平台无关的内容
['actions', 'dependencies', 'references', 'version'].forEach((key) => { for (let key of ['actions', 'dependencies', 'references', 'version']) {
if (app[key]) { if (app[key]) {
if (app[key][platform]) { if (app[key][platform]) {
app[key] = app[key][platform]; app[key] = app[key][platform];
...@@ -103,14 +95,15 @@ export class AppsService { ...@@ -103,14 +95,15 @@ export class AppsService {
app[key] = null; app[key] = null;
} }
} }
}); }
apps.set(item.id, app); apps.set(item.id, app);
} }
// 设置App关系 // 设置App关系
//TODO: 这里有必要重新整理一下么?
for (let [id] of apps) { for (let [id] of apps) {
let temp = (<App>apps.get(id))["actions"]; let temp = apps.get(id)!.actions;
let map = new Map<string,any>(); let map = new Map<string,any>();
for (let action of Object.keys(temp)) { for (let action of Object.keys(temp)) {
let openId = temp[action]["open"]; let openId = temp[action]["open"];
...@@ -119,23 +112,23 @@ export class AppsService { ...@@ -119,23 +112,23 @@ export class AppsService {
} }
map.set(action, temp[action]); map.set(action, temp[action]);
} }
(<App>apps.get(id)).actions = map; apps.get(id)!.actions = map;
['dependencies', 'references', 'parent'].forEach((key) => { for (let key of ['dependencies', 'references', 'parent']) {
let app = <App>apps.get(id); let app = apps.get(id)!;
let value = app[key]; let value = app[key];
if (value) { if (value) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
let map = new Map<string,App>(); let map = new Map<string,App>();
value.forEach((appId, index, array) => { for (let appId of value) {
map.set(appId, apps.get(appId)); map.set(appId, apps.get(appId));
}); }
app[key] = map; app[key] = map;
} else { } else {
app[key] = apps.get(value); app[key] = apps.get(value);
} }
} }
}); }
} }
return apps; return apps;
}; };
...@@ -197,14 +190,14 @@ export class AppsService { ...@@ -197,14 +190,14 @@ export class AppsService {
// } // }
async install(app: App, option: InstallOption) { async install(app: App, option: InstallOption) {
// TODO: 重构这个函数
const addDownloadTask = async(app: App, dir: 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) => { let metalink = await this.http.get(metalinkUrl).map((response) => response.text()).toPromise();
return response.text()
}).toPromise();
app.status.status = "downloading"; app.status.status = "downloading";
let downloadId = await this.downloadService.addMetalink(metalink, dir); let downloadId = await this.downloadService.addMetalink(metalink, dir);
let observable = this.downloadService.downloadProgress(downloadId); let observable = this.downloadService.downloadProgress(downloadId);
...@@ -265,7 +258,7 @@ export class AppsService { ...@@ -265,7 +258,7 @@ export class AppsService {
} catch (e) { } catch (e) {
for (let a of apps) { for (let a of apps) {
if (!a.isReady()) { if (!a.isReady()) {
a.status.status = 'init'; a.reset()
} }
} }
console.log(e); console.log(e);
...@@ -391,8 +384,11 @@ export class AppsService { ...@@ -391,8 +384,11 @@ export class AppsService {
remote.getCurrentWindow().minimize(); remote.getCurrentWindow().minimize();
} }
// TODO: 定位到下级文件,即现在的目录之内
// 如果有actions.main.execuate, 定位那个execuate的顶层路径
// 如果没有,定位目录里面任意一个顶级文件
browse(app: App) { browse(app: App) {
remote.shell.showItemInFolder((<AppLocal>app.local).path); remote.shell.showItemInFolder(app.local!.path);
} }
connections = new Map<App, Connection>(); connections = new Map<App, Connection>();
...@@ -464,27 +460,27 @@ export class AppsService { ...@@ -464,27 +460,27 @@ export class AppsService {
}; };
} }
tarPath: string; // tarPath: string;
installingId: string = ''; // installingId: string = '';
eventEmitter = new EventEmitter<void>(); eventEmitter = new EventEmitter<void>();
readonly checksumURL = "https://thief.mycard.moe/checksums/"; readonly checksumURL = "https://thief.mycard.moe/checksums/";
readonly updateServerURL = 'https://thief.mycard.moe/update/metalinks'; readonly updateServerURL = 'https://thief.mycard.moe/update/metalinks';
installQueue: Map<string,InstallTask> = new Map(); // installQueue: Map<string,InstallTask> = new Map();
map: Map<string,string> = new Map(); map: Map<string,string> = new Map();
private createId(): string { // private createId(): string {
function s4() { // function s4() {
return Math.floor((1 + Math.random()) * 0x10000) // return Math.floor((1 + Math.random()) * 0x10000)
.toString(16) // .toString(16)
.substring(1); // .substring(1);
} // }
//
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + // return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4(); // s4() + '-' + s4() + s4() + s4();
} // }
// installProgress(id: string): Observable<InstallStatus>|undefined { // installProgress(id: string): Observable<InstallStatus>|undefined {
// let app = this.map.get(id); // let app = this.map.get(id);
...@@ -493,6 +489,7 @@ export class AppsService { ...@@ -493,6 +489,7 @@ export class AppsService {
// } // }
// } // }
// TODO: push改个名
async push(task: InstallTask): Promise<void> { async push(task: InstallTask): Promise<void> {
if (!task.app.readyForInstall()) { if (!task.app.readyForInstall()) {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
...@@ -508,64 +505,69 @@ export class AppsService { ...@@ -508,64 +505,69 @@ export class AppsService {
await this.doInstall(task); await this.doInstall(task);
} }
// 调用前提:应用的依赖均已 Ready,应用处于下载完待安装的状态(waiting)。
// TODO: 要把Task系统去掉吗
async doInstall(task: InstallTask) { async doInstall(task: InstallTask) {
let app = task.app;
if (app.isWaiting()) {
console.error('doUninstall', "应用不处于等待安装状态", app);
throw("应用不处于等待安装状态");
}
if (!app.readyForInstall()) {
console.error('doInstall', "应用依赖不齐备", app);
throw("应用依赖不齐备");
}
try { try {
let app = task.app; let option = task.option;
let dependencies = app.findDependencies(); let installDir = option.installDir;
let readyForInstall = dependencies.every((dependency) => { let checksumFile = await this.getChecksumFile(app);
return dependency.isReady(); if (app.parent) {
}); // mod需要安装到parent路径
if (readyForInstall) { installDir = app.parent.local!.path;
let option = task.option; let parentFiles = new ComparableSet(Array.from(app.parent.local!.files.keys()));
let installDir = option.installDir; let appFiles = new ComparableSet(Array.from(checksumFile.keys()));
// if (!app.isInstalled()) { let conflictFiles = appFiles.intersection(parentFiles);
let checksumFile = await this.getChecksumFile(app); if (conflictFiles.size > 0) {
if (app.parent) { let backupPath = path.join(option.installLibrary, "backup", app.parent.id);
// mod需要安装到parent路径 await this.backupFiles(app.parent.local!.path, backupPath, conflictFiles);
installDir = app.parent.local!.path;
let parentFiles = new ComparableSet(Array.from(app.parent.local!.files.keys()));
let appFiles = new ComparableSet(Array.from(checksumFile.keys()));
let conflictFiles = appFiles.intersection(parentFiles);
if (conflictFiles.size > 0) {
let backupPath = path.join(option.installLibrary, "backup", app.parent.id);
await this.backupFiles(app.parent.local!.path, backupPath, conflictFiles);
}
}
let allFiles = new Set(checksumFile.keys());
app.status.status = "installing";
app.status.total = allFiles.size;
app.status.progress = 0;
// let timeNow = new Date().getTime();
for (let file of option.downloadFiles) {
await this.createDirectory(installDir);
let interval = setInterval(() => {
}, 500);
await new Promise((resolve, reject) => {
this.extract(file, installDir).subscribe(
(lastItem: string) => {
app.status.progress += 1;
app.status.progressMessage = lastItem;
},
(error) => {
reject(error);
},
() => {
resolve();
});
});
clearInterval(interval);
} }
await this.postInstall(app, installDir);
console.log("post install success");
let local = new AppLocal();
local.path = installDir;
local.files = checksumFile;
local.version = app.version;
app.local = local;
this.saveAppLocal(app);
app.status.status = "ready";
} }
// } let allFiles = new Set(checksumFile.keys());
app.status.status = "installing";
app.status.total = allFiles.size;
app.status.progress = 0;
// let timeNow = new Date().getTime();
for (let file of option.downloadFiles) {
await this.createDirectory(installDir);
let interval = setInterval(() => {
}, 500);
await new Promise((resolve, reject) => {
this.extract(file, installDir).subscribe(
(lastItem: string) => {
app.status.progress += 1;
app.status.progressMessage = lastItem;
},
(error) => {
reject(error);
},
() => {
resolve();
});
});
clearInterval(interval);
}
await this.postInstall(app, installDir);
console.log("post install success");
let local = new AppLocal();
local.path = installDir;
local.files = checksumFile;
local.version = app.version;
app.local = local;
this.saveAppLocal(app);
app.status.status = "ready";
} catch (e) { } catch (e) {
console.log("exception in doInstall", e); console.log("exception in doInstall", e);
throw e; throw e;
...@@ -576,10 +578,39 @@ export class AppsService { ...@@ -576,10 +578,39 @@ export class AppsService {
} }
createDirectory(dir: string) { // 移除mkdirp函数,在这里自己实现
return new Promise((resolve, reject) => { // 那个路径不存在且建立目录失败、或那个路径已经存在且不是目录,reject
mkdirp(dir, resolve); // 那个路径已经存在且是目录,返回false,那个路径不存在且成功建立目录,返回true
}) // TODO: 没测试
async createDirectory(dir: string): Promise<boolean> {
let stats: fs.Stats;
try {
stats = await new Promise<fs.Stats>((resolve, reject) => {
fs.stat(dir, (error, stats) => {
if (error) {
reject(error)
} else {
resolve(stats)
}
})
});
} catch (error) { // 路径不存在,先尝试递归上级目录,再创建自己
await this.createDirectory(path.dirname(dir));
return new Promise<boolean>((resolve, reject) => {
fs.mkdir(dir, (error) => {
if (error) {
reject(error)
} else {
resolve(true);
}
})
})
}
if (stats.isDirectory()) { // 路径存在并且已经是目录,成功返回
return false;
} else { // 路径存在并且不是目录,失败。
throw `#{dir} exists and is not a directory`
}
} }
extract(file: string, dir: string): Observable<string> { extract(file: string, dir: string): Observable<string> {
...@@ -603,22 +634,25 @@ export class AppsService { ...@@ -603,22 +634,25 @@ export class AppsService {
}) })
} }
// TODO: 与runApp合并,通用处理所有Action。
// shell: true的问题是DX特化,可以用写进app.json的方式
async postInstall(app: App, appPath: string) { async postInstall(app: App, appPath: string) {
let action = app.actions.get('install'); let action = app.actions.get('install');
if (action) { if (action) {
let env = Object.assign({}, action.env); let env = Object.assign({}, action.env);
let command: string[] = []; let command: string[] = [];
command.push(path.join(appPath, action.execute)); command.push(action.execute);
command.push(...action.args); command.push(...action.args);
let open = action.open; let open = action.open;
if (open) { if (open) {
let openAction: any = open.actions.get("main"); let openAction: any = open.actions.get("main");
env = Object.assign(env, openAction.env); env = Object.assign(env, openAction.env);
command.unshift(...openAction.args); command.unshift(...openAction.args);
command.unshift(path.join((<AppLocal>open.local).path, openAction.execute)); command.unshift(openAction.execute);
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let child = child_process.spawn(<string>command.shift(), command, { let child = child_process.spawn(command.shift()!, command, {
cwd: appPath,
env: env, env: env,
stdio: 'inherit', stdio: 'inherit',
shell: true, shell: true,
...@@ -708,6 +742,7 @@ export class AppsService { ...@@ -708,6 +742,7 @@ 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();
...@@ -715,31 +750,44 @@ export class AppsService { ...@@ -715,31 +750,44 @@ export class AppsService {
if (hasInstalledChild) { if (hasInstalledChild) {
throw "无法卸载,还有依赖此程序的游戏。" throw "无法卸载,还有依赖此程序的游戏。"
} }
if (app.isReady()) { if (app.isReady()) {
app.status.status = "uninstalling"; return this.doUninstall(app);
let appDir = app.local!.path; }
let files = Array.from(app.local!.files.keys()).sort().reverse(); }
app.status.total = files.length; // 调用前提:应用是 Ready, 不存在依赖这个应用的其他应用
async doUninstall(app: App) {
if (!app.isReady()) {
console.error('doUninstall', "应用不是 Ready 状态", app);
throw "应用不是 Ready 状态"
}
if (this.findChildren(app).find((child) => child.isInstalled())) {
console.error('doUninstall', "无法卸载,还有依赖此程序的游戏。", app);
throw "无法卸载,还有依赖此程序的游戏。"
}
for (let file of files) { app.status.status = "uninstalling";
app.status.progress += 1; let appDir = app.local!.path;
await this.deleteFile(path.join(appDir, file)); let files = Array.from(app.local!.files.keys()).sort().reverse();
}
if (app.parent) { app.status.total = files.length;
let backupDir = path.join(path.dirname(appDir), "backup", app.parent.id)
let fileSet = new ComparableSet(files); for (let file of files) {
let parentSet = new ComparableSet(Array.from(app.parent.local!.files.keys())); app.status.progress += 1;
let difference = parentSet.intersection(fileSet); await this.deleteFile(path.join(appDir, file));
if (difference) { }
this.restoreFiles(appDir, backupDir, Array.from(difference))
} if (app.parent) {
// TODO: 建立Library模型,把拼路径的事情交给Library
let backupDir = path.join(path.dirname(appDir), "backup", app.parent.id);
let fileSet = new ComparableSet(files);
let parentSet = new ComparableSet(Array.from(app.parent.local!.files.keys()));
let difference = parentSet.intersection(fileSet);
if (difference) {
this.restoreFiles(appDir, backupDir, Array.from(difference))
} }
app.local = null;
localStorage.removeItem(app.id);
} }
app.reset()
} }
} }
\ No newline at end of file
{ {
"name": "mycard", "name": "mycard",
"version": "3.0.0-dev.13", "version": "3.0.0-dev.14",
"description": "mycard", "description": "mycard",
"keywords": [], "keywords": [],
"author": "zh99998 <zh99998@gmail.com>", "author": "zh99998 <zh99998@gmail.com>",
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
"font-awesome": "latest", "font-awesome": "latest",
"ini": "latest", "ini": "latest",
"jquery": "latest", "jquery": "latest",
"mkdirp": "latest",
"raw-socket": "latest", "raw-socket": "latest",
"reflect-metadata": "latest", "reflect-metadata": "latest",
"rxjs": "5.0.0-beta.12", "rxjs": "5.0.0-beta.12",
...@@ -53,7 +52,6 @@ ...@@ -53,7 +52,6 @@
"@types/node": "latest", "@types/node": "latest",
"@types/electron": "latest", "@types/electron": "latest",
"@types/ini": "latest", "@types/ini": "latest",
"@types/mkdirp": "latest"
}, },
"build": { "build": {
"productName": "MyCard", "productName": "MyCard",
......
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