Commit 26170acd authored by wudizhanche1000's avatar wudizhanche1000

Fix: 安装卸载刷新 Add: 没有Ready自动禁用

parent 6305a6e2
......@@ -20,13 +20,13 @@
<!--应用ready-->
<div class="actions" *ngIf="currentApp.isReady() && (currentApp.id != 'ygopro')">
<button i18n *ngIf="currentApp.runable()" (click)="runApp(currentApp)" type="button" class="btn btn-primary">运行</button>
<button i18n *ngIf="currentApp.runable() && currentApp.actions.get('custom')" (click)="custom(currentApp)" type="button" class="btn btn-secondary">设置</button>
<button i18n *ngIf="currentApp.runnable()" (click)="runApp(currentApp)" [disabled]="!appsService.allReady(currentApp)" type="button" class="btn btn-primary">运行</button>
<button i18n *ngIf="currentApp.runnable() && currentApp.actions.get('custom')" [disabled]="!appsService.allReady(currentApp)" (click)="custom(currentApp)" type="button" class="btn btn-secondary">设置</button>
<div id="network" *ngIf="currentApp.network && currentApp.network.protocol == 'maotama'">
<div class="input-group">
<input *ngIf="appsService.connections.get(currentApp)" [value]="appsService.connections.get(currentApp).address || 'Loading...'" readonly type="text" class="form-control" title="address">
<div class="input-group-btn">
<button i18n *ngIf="!appsService.connections.get(currentApp)" (click)="appsService.network(currentApp, currentApp.network.servers[0])" type="button" class="btn btn-secondary">联机</button>
<button i18n *ngIf="!appsService.connections.get(currentApp)" [disabled]="!appsService.allReady(currentApp)" (click)="appsService.network(currentApp, currentApp.network.servers[0])" type="button" class="btn btn-secondary">联机</button>
<button i18n *ngIf="appsService.connections.get(currentApp)" (click)="copy(appsService.connections.get(currentApp).address)" [disabled]="!appsService.connections.get(currentApp).address" type="button" class="btn btn-secondary">复制</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" style="height: 38px;"></button>
<div class="dropdown-menu" [class.dropdown-menu-right]="appsService.connections.get(currentApp)">
......@@ -61,10 +61,10 @@
<th scope="row">{{i + 1}}</th>
<td>{{mod.name}}</td>
<td *ngIf="mod.isReady()">
<button i18n type="button" (click)="uninstall(mod)" class="btn btn-danger btn-sm">卸载</button>
<button i18n type="button" [disabled]="mod.isInstalled()&&!appsService.allReady(mod)" (click)="uninstall(mod)" class="btn btn-danger btn-sm">卸载</button>
</td>
<td *ngIf="!mod.isInstalled()">
<button i18n (click)="installMod(mod)" type="button" *ngIf="!mod.isInstalled()" class="btn btn-primary btn-sm">安装</button>
<button i18n (click)="installMod(mod)" [disabled]="mod.isInstalled()&&!appsService.allReady(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>
......@@ -75,9 +75,9 @@
</table>
</div>
<h2 i18n>本地文件</h2>
<button i18n (click)="appsService.browse(currentApp)" type="button" class="btn btn-secondary">浏览本地文件</button>
<button i18n (click)="appsService.browse(currentApp)" [disabled]="!appsService.allReady(currentApp)" type="button" class="btn btn-secondary">浏览本地文件</button>
<!--<button i18n type="button" (click)="verifyFiles(currentApp)" class="btn btn-secondary">校验完整性</button>-->
<button i18n (click)="uninstall(currentApp)" type="button" class="btn btn-secondary">卸载</button>
<button i18n (click)="uninstall(currentApp)" [disabled]="!appsService.allReady(currentApp)" type="button" class="btn btn-secondary">卸载</button>
</div>
<!--安装modal-->
......
......@@ -115,7 +115,7 @@ export class App {
return this.status.status === "uninstalling";
}
runable(): boolean {
runnable(): boolean {
return [Category.game].includes(this.category);
}
......
import {Injectable, ApplicationRef, EventEmitter} from "@angular/core";
import {Injectable, ApplicationRef, EventEmitter, NgZone} from "@angular/core";
import {Http} from "@angular/http";
import * as crypto from "crypto";
import {App, AppStatus, Action} from "./app";
......@@ -52,7 +52,7 @@ export class AppsService {
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,
private downloadService: DownloadService) {
private downloadService: DownloadService, private ngZone: NgZone) {
}
async loadApps() {
......@@ -247,6 +247,12 @@ export class AppsService {
return apps;
};
allReady(app: App) {
return app.isReady() &&
app.findDependencies().every((dependency) => dependency.isReady()) &&
this.findChildren(app).every((child) => (child.isInstalled() && child.isReady()) || !child.isInstalled());
}
sha256sum(file: string): Promise<string> {
return new Promise((resolve, reject) => {
let input = fs.createReadStream(file);
......@@ -258,9 +264,9 @@ export class AppsService {
reject(error);
});
hash.on('readable', () => {
let data = hash.read();
let data = <Buffer>hash.read();
if (data) {
resolve((<Buffer>data).toString("hex"));
resolve(data.toString("hex"));
}
});
input.pipe(hash);
......@@ -333,6 +339,12 @@ export class AppsService {
deletedFiles.add(file);
}
}
// changedFiles包含addedFiles,addedFiles仅供mod更新的时候使用。
for (let addedFile of addedFiles) {
changedFiles.add(addedFile);
}
let backupFiles: string[] = [];
let restoreFiles: string[] = [];
if (app.parent) {
......@@ -743,12 +755,19 @@ export class AppsService {
let option = task.option;
let installDir = option.installDir;
let checksumFile = await this.getChecksumFile(app);
let allFiles = new Set(checksumFile.keys());
app.status.status = "installing";
app.status.total = allFiles.size;
app.status.progress = 0;
let interval = setInterval(() => {
}, 500);
if (app.parent) {
// 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);
app.status.total += conflictFiles.size;
if (conflictFiles.size > 0) {
let backupPath = path.join(option.installLibrary, "backup", app.parent.id);
// 文件夹不需要备份,删除
......@@ -757,18 +776,23 @@ export class AppsService {
conflictFiles.delete(conflictFile);
}
}
await this.backupFiles(app.parent.local!.path, backupPath, conflictFiles);
await new Promise((resolve, reject) => {
this.ngZone.runOutsideAngular(async() => {
try {
await this.backupFiles(app.parent!.local!.path, backupPath, conflictFiles, (n) => {
app.status.progress += 1;
});
resolve();
} catch (e) {
reject(e);
}
});
});
}
}
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) => {
......@@ -782,8 +806,8 @@ export class AppsService {
resolve();
});
});
clearInterval(interval);
}
clearInterval(interval);
await this.postInstall(app, installDir);
console.log("post install success");
let local = new AppLocal();
......@@ -903,7 +927,8 @@ export class AppsService {
}
}
async backupFiles(dir: string, backupDir: string, files: Iterable<string>) {
async backupFiles(dir: string, backupDir: string, files: Iterable<string>, callback?: (progress: number)=>void) {
let n = 0;
for (let file of files) {
await new Promise(async(resolve, reject) => {
let srcPath = path.join(dir, file);
......@@ -912,19 +937,28 @@ export class AppsService {
fs.unlink(backupPath, (err) => {
fs.rename(srcPath, backupPath, resolve);
});
if (callback) {
callback(n)
}
n += 1;
});
}
}
async restoreFiles(dir: string, backupDir: string, files: Iterable<string>) {
async restoreFiles(dir: string, backupDir: string, files: Iterable<string>, callback?: (progress: number)=>{}) {
let n = 0;
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);
})
})
});
n += 1;
if (callback) {
callback(n);
}
});
}
}
......@@ -993,29 +1027,40 @@ export class AppsService {
console.error('doUninstall', "无法卸载,还有依赖此程序的游戏。", app);
throw "无法卸载,还有依赖此程序的游戏。"
}
app.status.status = "uninstalling";
let appDir = app.local!.path;
let files = Array.from(app.local!.files.keys()).sort().reverse();
app.status.total = files.length;
// 500毫秒手动刷新,避免文件过多产生的性能问题
let interval = setInterval(() => {
}, 500);
await new Promise((resolve, reject) => {
this.ngZone.runOutsideAngular(async() => {
try {
for (let file of files) {
app.status.progress += 1;
await this.deleteFile(path.join(appDir, file));
}
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) {
await this.restoreFiles(appDir, backupDir, Array.from(difference))
}
}
resolve();
}
catch (e) {
reject(e);
}
for (let file of files) {
app.status.progress += 1;
await this.deleteFile(path.join(appDir, file));
}
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) {
await this.restoreFiles(appDir, backupDir, Array.from(difference))
}
}
});
});
clearInterval(interval);
app.reset()
}
}
\ No newline at end of file
......@@ -5,19 +5,19 @@
<option *ngFor="let deck of decks" [ngValue]="deck">{{deck}}</option>
</select>
</div>
<button i18n type="submit" class="btn btn-secondary" (click)="edit_deck(current_deck)">编辑</button>
<button i18n type="submit" (click)="delete_deck(current_deck)" class="btn btn-secondary">删除</button>
<button i18n types="submit" (click)="refresh()" class="btn btn-secondary">刷新</button>
<button i18n [disabled]="!appsService.allReady(app)" type="submit" class="btn btn-secondary" (click)="edit_deck(current_deck)">编辑</button>
<button i18n [disabled]="!appsService.allReady(app)" type="submit" (click)="delete_deck(current_deck)" class="btn btn-secondary">删除</button>
<button i18n [disabled]="!appsService.allReady(app)" types="submit" (click)="refresh()" class="btn btn-secondary">刷新</button>
</form>
<div class="actions">
<button i18n (click)="request_match('athletic')" *ngIf="matching_arena != 'athletic'" [disabled]="matching" type="button" class="btn btn-primary">竞技匹配</button>
<button i18n (click)="cancel_match()" *ngIf="matching_arena == 'athletic'" type="button" class="btn btn-primary">取消等待</button>
<button i18n (click)="request_match('entertain')" *ngIf="matching_arena != 'entertain'" [disabled]="matching" type="button" class="btn btn-secondary">娱乐匹配</button>
<button i18n (click)="cancel_match()" *ngIf="matching_arena == 'entertain'" type="button" class="btn btn-secondary">取消等待</button>
<button i18n [disabled]="matching" type="button" class="btn btn-secondary" data-toggle="modal" data-target="#game-create-modal">创建房间</button>
<button i18n [disabled]="matching" type="button" class="btn btn-secondary" data-toggle="modal" data-target="#game-list-modal">房间列表</button>
<button i18n type="button" class="btn btn-secondary" data-toggle="modal" data-target="#game-create-windbot">单人模式</button>
<button i18n [disabled]="!appsService.allReady(app)" (click)="request_match('athletic')" *ngIf="matching_arena != 'athletic'" [disabled]="matching" type="button" class="btn btn-primary">竞技匹配</button>
<button i18n [disabled]="!appsService.allReady(app)" (click)="cancel_match()" *ngIf="matching_arena == 'athletic'" type="button" class="btn btn-primary">取消等待</button>
<button i18n [disabled]="!appsService.allReady(app)" (click)="request_match('entertain')" *ngIf="matching_arena != 'entertain'" [disabled]="matching" type="button" class="btn btn-secondary">娱乐匹配</button>
<button i18n [disabled]="!appsService.allReady(app)" (click)="cancel_match()" *ngIf="matching_arena == 'entertain'" type="button" class="btn btn-secondary">取消等待</button>
<button i18n [disabled]="matching||!appsService.allReady(app)" type="button" class="btn btn-secondary" data-toggle="modal" data-target="#game-create-modal">创建房间</button>
<button i18n [disabled]="matching||!appsService.allReady(app)" type="button" class="btn btn-secondary" data-toggle="modal" data-target="#game-list-modal">房间列表</button>
<button i18n [disabled]="!appsService.allReady(app)" type="button" class="btn btn-secondary" data-toggle="modal" data-target="#game-create-windbot">单人模式</button>
</div>
<div class="modal fade" id="game-create-windbot" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
......
......@@ -15,6 +15,7 @@ import {Http, Headers, URLSearchParams} from "@angular/http";
import "rxjs/Rx";
import {ISubscription} from "rxjs/Subscription";
import {SettingsService} from "./settings.sevices";
import {AppsService} from "./apps.service";
declare const $: any;
......@@ -108,7 +109,7 @@ export class YGOProComponent implements OnInit {
connections: WebSocket[] = [];
constructor(private http: Http, private settingsService: SettingsService, private loginService: LoginService, private ref: ChangeDetectorRef) {
constructor(private http: Http, private appsService: AppsService, private loginService: LoginService, private ref: ChangeDetectorRef) {
switch (process.platform) {
case 'darwin':
this.numfont = ['/System/Library/Fonts/SFNSTextCondensed-Bold.otf'];
......
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