Commit 73ceaafc authored by 神楽坂玲奈's avatar 神楽坂玲奈

fix

parents 71684161 a20ada6d
/bin/
/app/*.js
/app/*.js.map
/app/**/*.js
/app/**/*.js.map
/app/*.metadata.json
/node_modules/
/dist/
......
......@@ -10,6 +10,7 @@
<div>
<span *ngIf="currentApp.isDownloading()">正在下载</span>
<span *ngIf="currentApp.isInstalling()">正在安装...</span>
<span *ngIf="currentApp.isUninstalling()">正在卸载...</span>
<span *ngIf="currentApp.isWaiting()">等待安装...</span>
<span *ngIf="currentApp.status.total">{{(currentApp.status.progress/currentApp.status.total * 100).toFixed()}}%</span>
<span>{{currentApp.progressMessage()}}</span>
......@@ -59,13 +60,15 @@
<tr *ngFor="let mod of mods; let i = index">
<th scope="row">{{i + 1}}</th>
<td>{{mod.name}}</td>
<td *ngIf="mod.isInstalled()">
<td *ngIf="mod.isReady()">
<button i18n type="button" (click)="uninstall(mod)" class="btn btn-danger btn-sm">卸载</button>
</td>
<td *ngIf="!mod.isInstalled()">
<button i18n (click)="installMod(mod)" type="button" *ngIf="mod.status.status==='init'" class="btn btn-primary btn-sm">安装</button>
<progress *ngIf="mod.status.status==='downloading'" class="progress progress-striped progress-animated" value="{{mod.status.progress}}" max="{{mod.status.total}}"></progress>
<div i18n *ngIf="mod.status.status==='waiting'">等待安装...</div>
<button i18n (click)="installMod(mod)" type="button" *ngIf="!mod.isInstalled()" class="btn btn-primary btn-sm">安装</button>
</td>
<td *ngIf="mod.isInstalled()&&!mod.isReady()">
<progress class="progress progress-striped progress-animated" value="{{mod.status.progress}}" max="{{mod.status.total}}"></progress>
<!--<div i18n *ngIf="mod.isWaiting()">等待安装...</div>-->
</td>
</tr>
</tbody>
......@@ -91,8 +94,9 @@
<p i18n>即将开始安装 {{currentApp.name}}</p>
<h4 i18n>安装位置</h4>
<div class="form-group">
<select class="form-control" name="installPath" [(ngModel)]="installOption.installLibrary" title="path">
<select class="form-control" name="installPath" (change)="selectLibrary()" [(ngModel)]="installOption.installLibrary" title="path">
<option *ngFor="let library of libraries" value="{{library}}"> {{library}}</option>
<option *ngFor="let library of availableLibraries" value="create_{{library}}">在 {{library}}\ 盘新建 MyCard 库</option>
</select></div>
<h4 i18n>快捷方式</h4>
<div class="checkbox">
......@@ -104,10 +108,10 @@
<input id="create_desktop_shortcut" type="checkbox" name="desktop" [(ngModel)]="installOption.createDesktopShortcut">
<label i18n for="create_desktop_shortcut">创建桌面快捷方式</label>
</div>
<!--<h4 *ngIf="installOption.references.length">扩展内容</h4>-->
<!--<div *ngFor="let reference of installOption.references"><label>-->
<!--<input type="checkbox" [(ngModel)]="reference.install" name="references" value="{{reference.app.id}}"> {{reference.app.name}}-->
<!--</label></div>-->
<h4 *ngIf="references.length>0">扩展内容</h4>
<div *ngFor="let reference of references"><label>
<input type="checkbox" [(ngModel)]="referencesInstall[reference.id]" name="references"> {{reference.name}}
</label></div>
<div *ngIf="currentApp.findDependencies().length">
<span i18n>依赖:</span>
<span class="dependency" *ngFor="let dependency of currentApp.findDependencies()">{{dependency.name}}</span>
......
......@@ -6,7 +6,9 @@ import {App} from "./app";
import {DownloadService} from "./download.service";
import {clipboard, remote} from "electron";
import * as path from "path";
import * as fs from 'fs';
import {InstallService} from "./install.service";
import mkdirp = require("mkdirp");
declare const Notification: any;
declare const $: any;
......@@ -23,22 +25,61 @@ export class AppDetailComponent implements OnInit {
platform = process.platform;
installOption: InstallOption;
availableLibraries: string[] = [];
references: App[];
referencesInstall: {[id: string]: boolean};
constructor(private appsService: AppsService, private settingsService: SettingsService,
private downloadService: DownloadService, private installService: InstallService,
private ref: ChangeDetectorRef) {
}
// public File[] listRoots() {
// 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';
for (let i = 0; i < 26; i++) {
new Promise((resolve, reject) => {
let currentVolume = String.fromCharCode(volume.charCodeAt(0) + i) + ":";
fs.access(currentVolume, (err) => {
if (!err) {
//判断是否已经存在Library
if (this.libraries.every((library) => !library.startsWith(currentVolume))) {
this.availableLibraries.push(currentVolume);
}
}
})
})
}
}
updateInstallOption(app: App) {
this.installOption = new InstallOption(app);
this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path;
// this.installOption.references = [];
// for (let reference of app.references.values()) {
// this.installOption.references.push(new InstallOption(reference))
// }
this.references = Array.from(app.references.values());
console.log(this.references);
this.referencesInstall = {};
for (let reference of this.references) {
this.referencesInstall[reference.id] = true;
}
}
get libraries(): string[] {
......@@ -71,25 +112,39 @@ export class AppDetailComponent implements OnInit {
let options = this.installOption;
// if (options) {
// for (let reference of options.references) {
// if (reference.install && !reference.app.isInstalled()) {
// apps.push(reference.app);
// apps.push(...reference.app.findDependencies().filter((app) => {
// return !app.isInstalled()
// }))
// }
// }
// }
try {
this.appsService.install(this.currentApp, options);
await this.appsService.install(targetApp, options);
if (this.references.length > 0) {
for (let [id,isInstalled] of Object.entries(this.referencesInstall)) {
if (isInstalled) {
let reference = targetApp.references.get(id)!;
await this.appsService.install(reference, options);
}
}
}
} catch (e) {
console.error(e);
new Notification(targetApp.name, {body: "下载失败"});
}
}
async selectLibrary() {
if (this.installOption.installLibrary.startsWith('create_')) {
let volume = this.installOption.installLibrary.slice(7);
let library = path.join(volume, "MyCardLibrary");
try {
await this.installService.createDirectory(library);
this.installOption.installLibrary = library;
this.settingsService.addLibrary(library, true);
} catch (e) {
this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path;
alert("无法创建指定目录");
}
} else {
this.settingsService.setDefaultLibrary({path: this.installOption.installLibrary, "default": true})
}
}
selectDir() {
let dir = remote.dialog.showOpenDialog({properties: ['openFile', 'openDirectory']});
console.log(dir);
......
......@@ -100,6 +100,9 @@ export class App {
isDownloading(): boolean {
return this.status.status === "downloading";
}
isUninstalling():boolean{
return this.status.status==="uninstalling";
}
runable(): boolean {
return [Category.game].includes(this.category);
......
......@@ -10,10 +10,11 @@ import {remote} from "electron";
import "rxjs/Rx";
import {AppLocal} from "./app-local";
import * as ini from "ini";
import Timer = NodeJS.Timer;
import {DownloadService} from "./download.service";
import {InstallOption} from "./install-option";
import {InstallService} from "./install.service";
import Timer = NodeJS.Timer;
import {ComparableSet} from "./shared/ComparableSet";
const Aria2 = require('aria2');
const sudo = require('electron-sudo');
......@@ -116,6 +117,62 @@ export class AppsService {
return apps;
};
// async update(app: App) {
// const updateServer = "https://thief.mycard.moe/update/metalinks/";
//
// if (app.isReady() && app.local!.version != app.version) {
// let checksumMap = await this.installService.getChecksumFile(app)
//
// let latestFiles = new ComparableSet();
//
// }
//
// if (app.isInstalled() && app.version != (<AppLocal>app.local).version) {
// let checksumMap = await this.installService.getChecksumFile(app);
// let filesMap = (<AppLocal>app.local).files;
// let deleteList: string[] = [];
// let addList: string[] = [];
// let changeList: string[] = [];
// for (let [file,checksum] of filesMap) {
// let t = checksumMap.get(file);
// if (!t) {
// deleteList.push(file);
// } else if (t !== checksum) {
// changeList.push(file);
// }
// }
// for (let file of checksumMap.keys()) {
// if (!filesMap.has(file)) {
// changeList.push(file);
// }
// }
// let metalink = await this.http.post(updateServer + app.id, changeList).map((response) => response.text())
// .toPromise();
// let meta = new DOMParser().parseFromString(metalink, "text/xml");
// let filename = meta.getElementsByTagName('file')[0].getAttribute('name');
// let dir = path.join(path.dirname((<AppLocal>app.local).path), "downloading");
// let a = await this.downloadService.addMetalink(metalink, dir);
//
// for (let file of deleteList) {
// await this.installService.deleteFile(file);
// }
// (<AppLocal>app.local).version = app.version;
// (<AppLocal>app.local).files = checksumMap;
// localStorage.setItem(app.id, JSON.stringify(app.local));
// await this.installService.extract(path.join(dir, filename), (<AppLocal>app.local).path);
// let children = this.appsService.findChildren(app);
// for (let child of children) {
// if (child.isInstalled()) {
// await this.installService.uninstall(child, false);
// // this.installService.add(child, new InstallOption(child, path.dirname(((<AppLocal>app.local).path))));
// await this.installService.getComplete(child);
// console.log("282828")
// }
// }
//
// }
// }
async install(app: App, option: InstallOption) {
const addDownloadTask = async(app: App, dir: string) => {
let metalinkUrl = app.download;
......@@ -158,27 +215,30 @@ export class AppsService {
})
});
};
try {
let apps: App[] = [];
let dependencies = app.findDependencies();
apps.push(...dependencies, app);
let downloadPath = path.join(option.installLibrary, 'downloading');
let tasks: Promise<any>[] = [];
for (let a of apps) {
tasks.push(addDownloadTask(a, downloadPath));
}
let downloadResults = await Promise.all(tasks);
for (let result of downloadResults) {
console.log(result);
let o = new InstallOption(result.app, option.installLibrary);
o.downloadFiles = result.files;
this.installService.push({app: result.app, option: o});
if (!app.isInstalled()) {
try {
let apps: App[] = [];
let dependencies = app.findDependencies().filter((dependency) => {
return !dependency.isInstalled();
});
apps.push(...dependencies, app);
let downloadPath = path.join(option.installLibrary, 'downloading');
let tasks: Promise<any>[] = [];
for (let a of apps) {
tasks.push(addDownloadTask(a, downloadPath));
}
let downloadResults = await Promise.all(tasks);
for (let result of downloadResults) {
console.log(result);
let o = new InstallOption(result.app, option.installLibrary);
o.downloadFiles = result.files;
this.installService.push({app: result.app, option: o});
}
} catch (e) {
app.status.status = 'init';
console.log(e);
throw e;
}
// this.installService.push({app: app, option: option})
} catch (e) {
console.log(e);
throw e;
}
}
......@@ -231,12 +291,12 @@ export class AppsService {
}
if (action.open) {
let np2 = <App>action.open;
let np2 = action.open;
let openAction: Action;
openAction = <Action>np2.actions.get('main');
let openPath = (<AppLocal>np2.local).path;
openAction = np2.actions.get('main')!;
let openPath = np2.local!.path;
if (action.open.id == 'np2fmgen') {
const config_file = path.join((<AppLocal>(<App>action.open).local).path, 'np21nt.ini');
const config_file = path.join(action.open!.local!.path, 'np21nt.ini');
let config = await new Promise((resolve, reject) => {
fs.readFile(config_file, {encoding: 'utf-8'}, (error, data) => {
if (error) return reject(error);
......@@ -252,7 +312,7 @@ export class AppsService {
windtype: '0'
};
config['NekoProject21'] = Object.assign({}, default_config, config['NekoProject21']);
config['NekoProject21']['HDD1FILE'] = path.win32.join(process.platform == 'win32' ? '' : 'Z:', (<AppLocal>app.local).path, action.execute);
config['NekoProject21']['HDD1FILE'] = path.win32.join(process.platform == 'win32' ? '' : 'Z:', app.local!.path, action.execute);
await new Promise((resolve, reject) => {
fs.writeFile(config_file, ini.stringify(config), (error) => {
if (error) {
......@@ -262,12 +322,15 @@ export class AppsService {
}
})
});
args.push(openAction.execute);
args = args.concat(openAction.args);
let wine = <App>openAction.open;
openPath = (<AppLocal>wine.local).path;
openAction = <Action>(<App>openAction.open).actions.get('main');
cwd = (<AppLocal>np2.local).path;
if (process.platform != 'win32') {
args.push(openAction.execute);
args = args.concat(openAction.args);
let wine = openAction.open!;
openPath = wine.local!.path;
openAction = openAction!.open!.actions.get('main')!;
}
cwd = np2.local!.path;
}
args = args.concat(openAction.args);
args.push(action.execute);
......
......@@ -83,10 +83,12 @@ export class DownloadService {
let newDownloadSpeed = 0;
for (let [index,gid] of gidList.entries()) {
let task = this.taskList.get(gid)!;
statusList[index] = task.status;
newCompletedLength += parseInt(task.completedLength);
newTotalLength += parseInt(task.totalLength);
newDownloadSpeed += parseInt(task.downloadSpeed);
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;
......
......@@ -12,7 +12,7 @@ import * as fs from "fs";
import {EventEmitter} from "events";
import {AppLocal} from "./app-local";
import {Http} from "@angular/http";
import {AppsService} from "./apps.service";
import {ComparableSet} from "./shared/ComparableSet"
import ReadableStream = NodeJS.ReadableStream;
import {Observable, Observer} from "rxjs/Rx";
......@@ -33,7 +33,8 @@ export class InstallService {
installingId: string = '';
eventEmitter: EventEmitter = new EventEmitter();
checksumUri = "https://thief.mycard.moe/checksums/";
readonly checksumURL = "https://thief.mycard.moe/checksums/";
readonly updateServerURL = 'https://thief.mycard.moe/update/metalinks';
installQueue: Map<string,InstallTask> = new Map();
......@@ -90,19 +91,18 @@ export class InstallService {
});
if (readyForInstall) {
let option = task.option;
let installDir = option.installDir;
// if (!app.isInstalled()) {
let checksumFile = await this.getChecksumFile(app);
if (app.parent) {
let conflictFiles = new Set<string>();
let parentFilesMap = app.parent.local!.files;
for (let key of checksumFile.keys()) {
if (parentFilesMap.has(key)) {
conflictFiles.add(key);
}
}
// mod需要安装到parent路径
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);
this.backupFiles(option.installDir, backupPath, conflictFiles);
await this.backupFiles(app.parent.local!.path, backupPath, conflictFiles);
}
}
let allFiles = new Set(checksumFile.keys());
......@@ -111,17 +111,14 @@ export class InstallService {
app.status.progress = 0;
// let timeNow = new Date().getTime();
for (let file of option.downloadFiles) {
await this.createDirectory(option.installDir);
await this.createDirectory(installDir);
let interval = setInterval(() => {
}, 500);
await new Promise((resolve, reject) => {
this.extract(file, option.installDir).subscribe(
this.extract(file, installDir).subscribe(
(lastItem: string) => {
app.status.progress += 1;
app.status.progressMessage = lastItem;
// if (new Date().getTime() - timeNow > 500) {
// timeNow = new Date().getTime();
// }
},
(error) => {
reject(error);
......@@ -132,9 +129,9 @@ export class InstallService {
});
clearInterval(interval);
}
await this.postInstall(app, option.installDir);
await this.postInstall(app, installDir);
let local = new AppLocal();
local.path = option.installDir;
local.path = installDir;
local.files = checksumFile;
local.version = app.version;
app.local = local;
......@@ -230,21 +227,35 @@ export class InstallService {
}
}
async backupFiles(dir: string, backupPath: string, files: Iterable<string>) {
await this.createDirectory(backupPath);
async backupFiles(dir: string, backupDir: string, files: Iterable<string>) {
for (let file of files) {
await new Promise((resolve, reject) => {
let oldPath = path.join(dir, file);
let newPath = path.join(backupPath, file);
fs.rename(oldPath, newPath, resolve);
await new Promise(async(resolve, reject) => {
let srcPath = path.join(dir, file);
let backupPath = path.join(backupDir, file);
await this.createDirectory(path.dirname(backupPath));
fs.unlink(backupPath, (err) => {
fs.rename(srcPath, backupPath, resolve);
});
});
}
}
async restoreFiles(dir: string, backupDir: string, files: Iterable<string>) {
for (let file of files) {
await new Promise((resolve, reject) => {
let backupPath = path.join(backupDir, file);
let srcPath = path.join(dir, file);
fs.unlink(srcPath, (err) => {
fs.rename(backupPath, srcPath, resolve);
})
})
}
}
async getChecksumFile(app: App): Promise<Map<string,string> > {
let checksumUrl = this.checksumUri + app.id;
let checksumUrl = this.checksumURL + app.id;
if (["ygopro", 'desmume'].includes(app.id)) {
checksumUrl = this.checksumUri + app.id + "-" + process.platform;
checksumUrl = this.checksumURL + app.id + "-" + process.platform;
}
return this.http.get(checksumUrl)
.map((response) => {
......@@ -280,39 +291,33 @@ export class InstallService {
})
}
async uninstall(app: App, restore = true) {
if (!app.parent) {
// let children = this.appsService.findChildren(app);
// for (let child of children) {
// if (child.isInstalled()) {
// await this.uninstall(child);
// }
// }
}
let files = Array.from((<AppLocal>app.local).files.keys()).sort().reverse();
for (let file of files) {
let oldFile = file;
if (!path.isAbsolute(file)) {
oldFile = path.join((<AppLocal>app.local).path, file);
async uninstall(app: App) {
if (app.isReady()) {
app.status.status = "uninstalling";
let appDir = app.local!.path;
let files = Array.from(app.local!.files.keys()).sort().reverse();
app.status.total = files.length;
for (let file of files) {
app.status.progress += 1;
await this.deleteFile(path.join(appDir, file));
}
if (restore) {
await this.deleteFile(oldFile);
if (app.parent) {
let backFile = path.join((<AppLocal>app.local).path, "backup", file);
await new Promise((resolve, reject) => {
fs.rename(backFile, oldFile, resolve);
});
if (app.parent) {
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);
}
if (app.parent) {
await this.deleteFile(path.join((<AppLocal>app.local).path, "backup"));
} else {
await this.deleteFile((<AppLocal>app.local).path);
}
app.local = null;
localStorage.removeItem(app.id);
}
}
\ No newline at end of file
......@@ -43,9 +43,6 @@ export class LobbyComponent implements OnInit {
params.set('nickname', this.loginService.user.username);
params.set('autojoin', this.currentApp.conference + '@conference.mycard.moe');
this.candy_url = url;
// 尝试更新应用
this.updateApp();
}
chooseApp(app: App) {
......@@ -55,56 +52,7 @@ export class LobbyComponent implements OnInit {
}
}
async updateApp() {
let updateServer = "https://thief.mycard.moe/update/metalinks/";
let checksumServer = "https://thief.mycard.moe/checksums/";
for (let app of this.apps.values()) {
if (app.isInstalled() && app.version != (<AppLocal>app.local).version) {
let checksumMap = await this.installService.getChecksumFile(app);
let filesMap = (<AppLocal>app.local).files;
let deleteList: string[] = [];
let addList: string[] = [];
let changeList: string[] = [];
for (let [file,checksum] of filesMap) {
let t = checksumMap.get(file);
if (!t) {
deleteList.push(file);
} else if (t !== checksum) {
changeList.push(file);
}
}
for (let file of checksumMap.keys()) {
if (!filesMap.has(file)) {
changeList.push(file);
}
}
let metalink = await this.http.post(updateServer + app.id, changeList).map((response) => response.text())
.toPromise();
let meta = new DOMParser().parseFromString(metalink, "text/xml");
let filename = meta.getElementsByTagName('file')[0].getAttribute('name');
let dir = path.join(path.dirname((<AppLocal>app.local).path), "downloading");
let a = await this.downloadService.addMetalink(metalink, dir);
for (let file of deleteList) {
await this.installService.deleteFile(file);
}
(<AppLocal>app.local).version = app.version;
(<AppLocal>app.local).files = checksumMap;
localStorage.setItem(app.id, JSON.stringify(app.local));
await this.installService.extract(path.join(dir, filename), (<AppLocal>app.local).path);
let children = this.appsService.findChildren(app);
for (let child of children) {
if (child.isInstalled()) {
await this.installService.uninstall(child, false);
// this.installService.add(child, new InstallOption(child, path.dirname(((<AppLocal>app.local).path))));
await this.installService.getComplete(child);
console.log("282828")
}
}
}
}
}
get grouped_apps() {
......
......@@ -36,6 +36,28 @@ export class SettingsService {
return this.libraries;
}
addLibrary(libraryPath: string, isDefault: boolean) {
let libraries = this.getLibraries();
if (isDefault) {
libraries.forEach((l) => {
l.default = false;
});
}
libraries.push({"default": isDefault, path: libraryPath});
this.libraries = libraries;
localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries));
}
setDefaultLibrary(library: Library) {
let libraries = this.getLibraries();
libraries.forEach((l) => {
l.default = library.path == l.path;
});
this.libraries = libraries;
localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries));
}
getDefaultLibrary(): Library {
if (!this.libraries) {
this.getLibraries()
......
/**
* Created by weijian on 2016/12/5.
*/
export class ComparableSet<T> extends Set<T> {
constructor(values?: Iterable<T>) {
if (values) {
super(values);
} else {
super();
}
}
isSuperset(subset: Set<T>) {
for (var elem of subset) {
if (!this.has(elem)) {
return false;
}
}
return true;
}
union(setB: Set<T>): Set<T> {
var union = new Set(this);
for (var elem of setB) {
union.add(elem);
}
return union;
}
intersection(setB: Set<T>): Set<T> {
var intersection = new Set();
for (var elem of setB) {
if (this.has(elem)) {
intersection.add(elem);
}
}
return intersection;
}
difference(setB: Set<T>): Set<T> {
var difference = new Set(this);
for (var elem of setB) {
difference.delete(elem);
}
return difference;
}
}
......@@ -2070,7 +2070,8 @@
}
},
"version": {
"darwin": "1.06"
"win32": "1.033.C-7",
"darwin": "1.033.C-7"
},
"news": [
{
......
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