Commit 10141262 authored by 神楽坂玲奈's avatar 神楽坂玲奈

Merge branch 'v3' of github.com:mycard/mycard into v3

parents 2e4df7f6 97fd7c71
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
<td *ngIf="!mod.isInstalled()"> <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)" type="button" *ngIf="!mod.isInstalled()" class="btn btn-primary btn-sm">安装</button>
</td> </td>
<td *ngIf="mod.isInstalled()&&!mod.isReady()">: <td *ngIf="mod.isInstalled()&&!mod.isReady()">
<progress class="progress progress-striped progress-animated" value="{{mod.status.progress}}" max="{{mod.status.total}}"></progress> <progress class="progress progress-striped progress-animated" value="{{mod.status.progress}}" max="{{mod.status.total}}"></progress>
<!--<div i18n *ngIf="mod.isWaiting()">等待安装...</div>--> <!--<div i18n *ngIf="mod.isWaiting()">等待安装...</div>-->
</td> </td>
......
...@@ -145,8 +145,18 @@ export class AppDetailComponent implements OnInit { ...@@ -145,8 +145,18 @@ export class AppDetailComponent implements OnInit {
this.appsService.runApp(app, 'custom'); this.appsService.runApp(app, 'custom');
} }
verifyFiles(app: App) { async verifyFiles(app: App) {
this.appsService.verifyFiles(app); try {
await this.appsService.update(app, true);
let installedMods = this.appsService.findChildren(app).filter((child) => {
return child.parent === app && child.isInstalled() && child.isReady();
});
for (let mod of installedMods) {
await this.appsService.update(mod, true);
}
} catch (e) {
console.log(e);
}
} }
copy(text: string) { copy(text: string) {
......
...@@ -267,39 +267,29 @@ export class AppsService { ...@@ -267,39 +267,29 @@ export class AppsService {
}); });
} }
async verifyFiles(app: App) { async verifyFiles(app: App, checksumFiles: Map<string,string>): Promise<Map<string,string>> {
if (app.isReady()) { let result = new Map<string,string>();
app.status.status = "updating"; for (let [file,checksum] of checksumFiles) {
Logger.info("Start to verify files: ", app); let filePath = path.join(app.local!.path, file);
let lastedFile = await this.getChecksumFile(app); // 如果文件不存在,随便生成一个checksum
await new Promise((resolve, reject) => {
let changedList: string[] = []; fs.access(filePath, fs.constants.F_OK, async(err: Error) => {
if (err) {
for (let [file,checksum] of lastedFile) { result.set(file, Math.random().toString());
let absolutePath = path.join(app.local!.path, file); } else if (checksum === "") {
await new Promise((resolve, reject) => { result.set(file, "");
fs.access(absolutePath, fs.constants.F_OK, (err) => { } else {
if (err) { let sha256sum = await this.sha256sum(filePath);
changedList.push(file); result.set(file, sha256sum);
}
resolve();
});
});
if (checksum !== "") {
let c = await this.sha256sum(file);
if (c !== checksum) {
changedList.push(file);
} }
} resolve();
} });
await this.doUpdate(app, changedList); });
app.status.status = "ready";
} }
return result;
} }
async update(app: App) { async update(app: App, verify = false) {
let readyToUpdate: boolean = false; let readyToUpdate: boolean = false;
// 已经安装的mod // 已经安装的mod
let mods = this.findChildren(app).filter((mod) => { let mods = this.findChildren(app).filter((mod) => {
...@@ -307,91 +297,112 @@ export class AppsService { ...@@ -307,91 +297,112 @@ export class AppsService {
}); });
// 如果是不是mod,那么要所有已经安装mod都ready // 如果是不是mod,那么要所有已经安装mod都ready
// 如果是mod,那么要parent ready // 如果是mod,那么要parent ready
if (app.parent && app.parent.isReady()) { if (app.parent && app.parent.isReady() && app.isReady()) {
readyToUpdate = true; readyToUpdate = true;
} else { } else {
readyToUpdate = mods.every((mod) => { readyToUpdate = app.isReady() && mods.every((mod) => mod.isReady());
return mod.isReady();
})
} }
if (readyToUpdate && app.local!.version !== app.version) { if (readyToUpdate && (app.local!.version !== app.version || verify)) {
app.status.status = "updating"; app.status.status = "updating";
Logger.info("Checking updating: ", app); try {
let latestFiles = await this.getChecksumFile(app); Logger.info("Checking updating: ", app);
let localFiles = app.local!.files; let latestFiles = await this.getChecksumFile(app);
let changedFiles: Set<string> = new Set<string>(); let localFiles: Map<string,string>;
let deletedFiles: Set<string> = new Set<string>(); if (verify) {
for (let [file,checksum] of latestFiles) { localFiles = await this.verifyFiles(app, latestFiles);
if (!localFiles.has(file)) { } else {
changedFiles.add(file); localFiles = app.local!.files;
} }
} let addedFiles: Set<string> = new Set<string>();
for (let [file,checksum] of localFiles) { let changedFiles: Set<string> = new Set<string>();
if (latestFiles.has(file)) { let deletedFiles: Set<string> = new Set<string>();
if (latestFiles.get(file) !== checksum) { // 遍历寻找新增加的文件
for (let [file,checksum] of latestFiles) {
if (!localFiles.has(file)) {
changedFiles.add(file); changedFiles.add(file);
addedFiles.add(file);
} }
} else {
deletedFiles.add(file);
} }
} // 遍历寻找旧版本与新版本不一样的文件和新版本比旧版少了的文件
let backupFiles: string[] = []; for (let [file,checksum] of localFiles) {
let restoreFiles: string[] = []; if (latestFiles.has(file)) {
if (app.parent) { if (latestFiles.get(file) !== checksum) {
let parentFiles = app.parent.local!.files; changedFiles.add(file);
// 添加的文件和parent冲突,且不是目录,就添加到conflict列表 }
for (let changedFile of changedFiles) { } else {
if (parentFiles.has(changedFile) && parentFiles.get(changedFile) !== "") { deletedFiles.add(file);
backupFiles.push(changedFile);
} }
} }
//如果要删除的文件parent里也有就恢复这个文件 let backupFiles: string[] = [];
for (let deletedFile of deletedFiles) { let restoreFiles: string[] = [];
restoreFiles.push(deletedFile); if (app.parent) {
} let parentFiles = app.parent.local!.files;
// 新增加的文件和parent冲突,且不是目录,就添加backup到
let backupDir = path.join(path.dirname(app.local!.path), "backup", app.parent.id); // 改变的文件不做备份
await this.backupFiles(app.local!.path, backupDir, backupFiles); for (let addedFile of addedFiles) {
await this.restoreFiles(app.local!.path, backupDir, restoreFiles); if (parentFiles.has(addedFile) && parentFiles.get(addedFile) !== "") {
} else { backupFiles.push(addedFile);
for (let mod of mods) {
// 如果changed列表与已经安装的mod有冲突,就push到back列表里
for (let changedFile of changedFiles) {
if (mod.local!.files.has(changedFile)) {
backupFiles.push(changedFile);
} }
} }
// 如果要删除的文件,mod里面存在,就不要删除这个文件 //如果要删除的文件parent里也有就恢复这个文件
for (let deletedFile of deletedFiles) { for (let deletedFile of deletedFiles) {
if (mod.local!.files.has(deletedFile)) { restoreFiles.push(deletedFile);
deletedFiles.delete(deletedFile);
}
} }
let backupDir = path.join(path.dirname(app.local!.path), "mods_backup", app.id);
}
}
// 筛选更新文件中与mod冲突的部分 let backupDir = path.join(path.dirname(app.local!.path), "backup", app.parent.id);
for (let mod of installedMods) { await this.backupFiles(app.local!.path, backupDir, backupFiles);
changedFiles.forEach((changedFile) => { await this.restoreFiles(app.local!.path, backupDir, restoreFiles);
if (mod.local!.files.has(changedFile)) { } else {
conflictFiles.add(changedFile); for (let mod of mods) {
} // 更新时,冲突文件在backup目录里,需要更新backup目录里的文件
}); // 如果changed列表与已经安装的mod有冲突,就push到backup列表里
deletedFiles.forEach((deletedFile) => { // 然后先把当前的mod文件被分到mods_backup目录再解压更新,把文件备份到backup,最后从mods_backup里恢复mods文件
if (mod.local!.files.has(deletedFile)) {
deletedFiles.delete(deletedFile); // 校验时,认为mod的文件正确,把冲突文件从changed列表里面删除掉
for (let changedFile of changedFiles) {
if (mod.local!.files.has(changedFile)) {
if (!verify) {
backupFiles.push(changedFile);
} else {
changedFiles.delete(changedFile);
}
}
}
let backupToDelete: string[] = [];
// 如果要删除的文件,mod里面存在,就删除backup目录里的文件
for (let deletedFile of deletedFiles) {
if (mod.local!.files.has(deletedFile)) {
backupToDelete.push(deletedFile);
}
}
let backupDir = path.join(path.dirname(app.local!.path), "mods_backup", app.id);
await this.backupFiles(app.local!.path, backupDir, backupFiles);
for (let file of backupToDelete) {
await this.deleteFile(path.join(app.local!.path, file))
}
} }
}) }
await this.doUpdate(app, changedFiles, deletedFiles);
Logger.info("Update extract finished");
//如果不是mod,就先把自己目录里最新的冲突文件backup到backup目录
//再把mods_backup里面的文件恢复到游戏目录
if (!app.parent) {
Logger.info("Start to restore files...");
let modsBackupDir = path.join(path.dirname(app.local!.path), "mods_backup", app.id);
let appBackupDir = path.join(path.dirname(app.local!.path), "backup", app.id);
await this.backupFiles(app.local!.path, appBackupDir, backupFiles);
await this.restoreFiles(app.local!.path, modsBackupDir, backupFiles);
}
app.local!.version = app.version;
app.local!.files = latestFiles;
this.saveAppLocal(app);
app.status.status = "ready";
Logger.info("Update Finished: ", app);
} catch (e) {
Logger.error("Update Failed: ", e);
app.status.status = "ready";
} }
await this.doUpdate(app, changedFiles, deletedFiles);
app.local!.version = app.version;
app.local!.files = latestFiles;
this.saveAppLocal(app);
app.status.status = "ready";
installedMods.forEach((mod) => mod.status.status = "ready");
Logger.info("Update Finished: ", app);
} }
} }
...@@ -405,15 +416,13 @@ export class AppsService { ...@@ -405,15 +416,13 @@ export class AppsService {
} }
let metalink = await this.http.post(updateUrl, changedFiles).map((response) => response.text()).toPromise(); let metalink = await this.http.post(updateUrl, changedFiles).map((response) => response.text()).toPromise();
let downloadDir = path.join(path.dirname(app.local!.path), "downloading"); let downloadDir = path.join(path.dirname(app.local!.path), "downloading");
let downloadId = await let downloadId = await this.downloadService.addMetalink(metalink, downloadDir);
this.downloadService.addMetalink(metalink, downloadDir); await this.downloadService.progress(downloadId, (status: DownloadStatus) => {
await app.status.progress = status.completedLength;
this.downloadService.progress(downloadId, (status: DownloadStatus) => { app.status.total = status.totalLength;
app.status.progress = status.completedLength; app.status.progressMessage = status.downloadSpeedText;
app.status.total = status.totalLength; this.ref.tick();
app.status.progressMessage = status.downloadSpeedText; });
this.ref.tick();
});
let downloadFiles = await this.downloadService.getFiles(downloadId); let downloadFiles = await this.downloadService.getFiles(downloadId);
for (let downloadFile of downloadFiles) { for (let downloadFile of downloadFiles) {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
...@@ -433,6 +442,7 @@ export class AppsService { ...@@ -433,6 +442,7 @@ export class AppsService {
await this.deleteFile(path.join(app.local!.path, deletedFile)); await this.deleteFile(path.join(app.local!.path, deletedFile));
} }
} }
} }
async install(app: App, option: InstallOption) { async install(app: App, option: InstallOption) {
...@@ -462,12 +472,16 @@ export class AppsService { ...@@ -462,12 +472,16 @@ export class AppsService {
app.status.status = "downloading"; app.status.status = "downloading";
let metalink = await this.http.get(metalinkUrl).map((response) => response.text()).toPromise(); 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);
await this.downloadService.progress(downloadId, (status: DownloadStatus) => { try {
app.status.progress = status.completedLength; await this.downloadService.progress(downloadId, (status: DownloadStatus) => {
app.status.total = status.totalLength; app.status.progress = status.completedLength;
app.status.progressMessage = status.downloadSpeedText; app.status.total = status.totalLength;
this.ref.tick(); app.status.progressMessage = status.downloadSpeedText;
}); this.ref.tick();
});
} catch (e) {
}
let files = await this.downloadService.getFiles(downloadId); let files = await this.downloadService.getFiles(downloadId);
app.status.status = "waiting"; app.status.status = "waiting";
return {app: app, files: files} return {app: app, files: files}
......
...@@ -112,6 +112,7 @@ export class DownloadService { ...@@ -112,6 +112,7 @@ export class DownloadService {
let activeList = await this.aria2.tellActive(); let activeList = await this.aria2.tellActive();
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);
this.downloadList.clear();
for (let item of activeList) { for (let item of activeList) {
this.downloadList.set(item.gid, new DownloadStatus(item)); this.downloadList.set(item.gid, new DownloadStatus(item));
} }
...@@ -142,33 +143,41 @@ export class DownloadService { ...@@ -142,33 +143,41 @@ export class DownloadService {
let gids = this.taskMap.get(id); let gids = this.taskMap.get(id);
if (gids) { if (gids) {
let allStatus: DownloadStatus; let allStatus: DownloadStatus;
this.updateEmitter.subscribe(() => { let subscription = this.updateEmitter.subscribe(() => {
let status: DownloadStatus = new DownloadStatus();
// 合并每个状态信息 try {
status = let status: DownloadStatus = new DownloadStatus();
gids!.map((value, index, array) => { // 合并每个状态信息
let s = this.downloadList.get(value); status =
if (!s) { gids!.map((value, index, array) => {
throw new Error("Gid not Exists"); let s = this.downloadList.get(value);
} if (!s) {
return s; throw "Gid not exists";
}) }
.reduce((previousValue, currentValue, currentIndex, array) => { return s;
return previousValue.combine(currentValue); })
}, status); .reduce((previousValue, currentValue, currentIndex, array) => {
if (!allStatus) { return previousValue.combine(currentValue);
allStatus = status; }, status);
} else { if (!allStatus) {
if (allStatus.compareTo(status) != 0) {
allStatus = status; allStatus = status;
} else {
if (allStatus.compareTo(status) != 0) {
allStatus = status;
}
} }
} if (allStatus.status === "error") {
if (allStatus.status === "error") { throw `Download Error: code ${allStatus.errorCode}, message: ${allStatus.errorMessage}`;
reject(`Download Error: code ${allStatus.errorCode}, message: ${allStatus.errorMessage}`); } else if (allStatus.status === "complete") {
} else if (allStatus.status === "complete") { resolve();
resolve(); subscription.unsubscribe();
} else { } else {
callback(allStatus); callback(allStatus);
}
} catch (e) {
reject(e);
subscription.unsubscribe();
} }
}); });
} else { } else {
......
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