Commit e8298003 authored by nanahira's avatar nanahira

everything complete

parent c339d03b
import { Controller, Get, Param, Query, Render, ValidationPipe } from '@nestjs/common';
import { Body, Controller, Get, Param, ParseArrayPipe, Post, Query, Render, ValidationPipe } from '@nestjs/common';
import { UpdateService } from './update.service';
import { ApiOkResponse, ApiOperation, ApiParam, ApiProperty, ApiQuery, ApiTags } from '@nestjs/swagger';
import { ApiBody, ApiOkResponse, ApiOperation, ApiParam, ApiProperty, ApiQuery, ApiTags } from '@nestjs/swagger';
import { DepotDto } from '../dto/Depot.dto';
@Controller('update')
......@@ -42,4 +42,22 @@ export class UpdateController {
) {
return this.updateService.getFullPackageMetalink(id, depot, version);
}
@Post('update/:id/:version')
@Render('metalinks')
@ApiOperation({ summary: '获取 app 部分包 metalink', description: '根据文件返回需要下载什么文件' })
@ApiParam({ name: 'id', description: 'APP 的 id' })
@ApiParam({ name: 'version', description: 'APP 的版本号' })
@ApiQuery({ type: DepotDto, description: 'APP 的类型' })
@ApiBody({ type: [String], description: '需要什么文件' })
@ApiOkResponse({ type: String })
async getPartPackageMetalink(
@Param('id') id: string,
@Query(new ValidationPipe({ transform: true })) depot: DepotDto,
@Param('version') version: string,
@Body(new ParseArrayPipe()) requestedFiles: string[]
) {
//return requestedFiles;
return this.updateService.getPartPackageMetalink(id, depot, version, requestedFiles);
}
}
......@@ -5,8 +5,9 @@ import { App } from '../entities/App.entity';
import { DepotDto } from '../dto/Depot.dto';
import { Build } from '../entities/Build.entity';
import { BlankReturnMessageDto } from '../dto/ReturnMessage.dto';
import { ArchiveType } from '../entities/Archive.entity';
import { Archive, ArchiveType } from '../entities/Archive.entity';
import { PackageS3Service } from '../package-s3/package-s3.service';
import _ from 'lodash';
@Injectable()
export class UpdateService extends ConsoleLogger {
......@@ -24,14 +25,21 @@ export class UpdateService extends ConsoleLogger {
return (await this.db.getRepository(App).find({ where: { appData: Not(IsNull()), isDeleted: false } })).map((a) => a.appData);
}
private async getBuild(id: string, depotDto: DepotDto, version: string, extraQuery?: (query: SelectQueryBuilder<Build>) => void) {
private async getBuild(
id: string,
depotDto: DepotDto,
version: string,
extraQuery?: (query: SelectQueryBuilder<Build>) => void,
noErrorExit = false
) {
const depotObj = depotDto.toActual;
const query = this.db
.getRepository(Build)
.createQueryBuilder('build')
//.select('build.id', 'id')
//.addSelect('build.checksum', 'checksum')
.innerJoin('build.depot', 'depot')
.innerJoin('depot.app', 'app')
.leftJoinAndSelect('build.archives', 'archive')
.where('app.id = :id', { id })
.andWhere('app.isDeleted = false')
.andWhere('depot.platform = :platform')
......@@ -43,12 +51,43 @@ export class UpdateService extends ConsoleLogger {
extraQuery(query);
}
const build = await query.getOne();
if (!build) {
if (!noErrorExit && !build) {
throw new BlankReturnMessageDto(404, 'Build not found').toException();
}
return build;
}
private async getArchives(
id: string,
depotDto: DepotDto,
version: string,
extraQuery?: (query: SelectQueryBuilder<Archive>) => void,
noErrorExit = false
) {
const depotObj = depotDto.toActual;
const query = this.db
.getRepository(Archive)
.createQueryBuilder('archive')
.innerJoin('archive.build', 'build')
.innerJoin('build.depot', 'depot')
.innerJoin('depot.app', 'app')
.where('app.id = :id', { id })
.andWhere('app.isDeleted = false')
.andWhere('depot.platform = :platform')
.andWhere('depot.arch = :arch')
.andWhere('depot.locale = :locale')
.andWhere('build.version = :version', { version })
.setParameters(depotObj);
if (extraQuery) {
extraQuery(query);
}
const archives = await query.getMany();
if (!noErrorExit && !archives.length) {
throw new BlankReturnMessageDto(404, 'Build not found').toException();
}
return archives;
}
async getChecksum(id: string, depotDto: DepotDto, version: string) {
const build = await this.getBuild(id, depotDto, version);
return {
......@@ -59,12 +98,83 @@ export class UpdateService extends ConsoleLogger {
}
async getFullPackageMetalink(id: string, depotDto: DepotDto, version: string) {
const build = await this.getBuild(id, depotDto, version, (qb) =>
const archives = await this.getArchives(id, depotDto, version, (qb) =>
qb.andWhere('archive.role = :fullRole', { fullRole: ArchiveType.Full })
);
return {
cdnUrl: this.cdnUrl,
archives: build.archives,
archives: archives,
};
}
private getPartArchiveSubset(
requestedFiles: string[],
allArchives: Archive[],
currentSize = 0,
currentBestSolutionValue = Infinity
): [Archive[], number] {
/*
this.log(
`requested: ${requestedFiles.join(',')} remaining: ${allArchives
.map((a) => `${a.size}:${a.files.join(',')}`)
.join('|')} currentSize: ${currentSize} currentBestSolutionValue: ${currentBestSolutionValue}`
);
*/
if (!requestedFiles.length) {
return [[], currentSize];
}
let bestSolution: [Archive[], number] = [null, currentBestSolutionValue];
for (let i = 0; i < allArchives.length; ++i) {
const archive = allArchives[i];
const nextStepSize = currentSize + archive.size;
if (nextStepSize > bestSolution[1]) {
// 加一个包就太大了的话,不考虑这个方案了
continue;
}
const remainingFiles = _.difference(requestedFiles, archive.files);
const remainingArchives = allArchives.slice(i + 1);
const nextStepResult = this.getPartArchiveSubset(remainingFiles, remainingArchives, nextStepSize, bestSolution[1]);
if (nextStepResult[0] !== null && nextStepResult[1] < bestSolution[1]) {
nextStepResult[0].push(archive);
bestSolution = nextStepResult;
// this.log(`Got better: ${nextStepResult[0].map((a) => `${a.size}:${a.files.join(',')}`).join('|')} ${nextStepResult[1]}`);
}
}
return bestSolution;
}
async getPartPackageMetalink(id: string, depotDto: DepotDto, version: string, requestedFiles: string[]) {
const tryExactArchives = await this.getArchives(
id,
depotDto,
version,
(qb) =>
qb
.andWhere('archive.role = :updateRole', { updateRole: ArchiveType.Update })
.andWhere(':requestedFiles = archive.files', { requestedFiles: requestedFiles })
.orderBy('archive.size', 'ASC')
.limit(1),
true
);
if (tryExactArchives.length) {
return {
cdnUrl: this.cdnUrl,
archives: tryExactArchives,
};
}
const allArchives = await this.getArchives(id, depotDto, version, (qb) =>
qb.andWhere(':requestedFiles && archive.files', { requestedFiles: requestedFiles }).orderBy('archive.size', 'ASC')
);
/*for (const archive of allArchives) {
archive.files = _.intersection(archive.files, requestedFiles);
}*/
const [archives] = this.getPartArchiveSubset(_.uniq(requestedFiles), allArchives);
if (!archives) {
throw new BlankReturnMessageDto(404, 'Some files of this build not found').toException();
}
return {
cdnUrl: this.cdnUrl,
archives,
};
}
}
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