2020-11-16 16:55:04 +01:00
|
|
|
import Controller from "swaf/Controller";
|
|
|
|
import {NextFunction, Request, Response} from "express";
|
|
|
|
import * as https from "https";
|
|
|
|
import config from "config";
|
|
|
|
import {log} from "swaf/Logger";
|
2020-11-20 13:38:32 +01:00
|
|
|
import {NotFoundHttpError, ServiceUnavailableHttpError} from "swaf/HttpError";
|
2020-11-20 13:21:56 +01:00
|
|
|
import * as fs from "fs";
|
|
|
|
import {promisify} from "util";
|
|
|
|
import path from "path";
|
|
|
|
|
|
|
|
export const ASSETS_BASE_DIR = config.get<string>('assets_base_dir');
|
2020-11-16 16:55:04 +01:00
|
|
|
|
|
|
|
export default class GiteaRepoLatestReleaseController extends Controller {
|
|
|
|
public routes(): void {
|
|
|
|
this.get('/:owner/:name/:file?', this.getFile, 'get-repo-release-file');
|
|
|
|
}
|
|
|
|
|
|
|
|
protected async getFile(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
|
|
log.info('Serving ' + req.path + ' ...');
|
|
|
|
const {owner, name, file} = req.params;
|
|
|
|
if (!owner || !name) return next();
|
|
|
|
|
2020-11-20 13:21:56 +01:00
|
|
|
const httpRequest = https.get(`${config.get('gitea_instance_url')}/api/v1/repos/${owner}/${name}/releases`, {
|
2020-11-16 16:55:04 +01:00
|
|
|
headers: {
|
|
|
|
'Accept': 'application/json',
|
|
|
|
},
|
|
|
|
}, r => {
|
|
|
|
let data = '';
|
|
|
|
r.on('data', c => {
|
|
|
|
data += c;
|
|
|
|
});
|
2020-11-20 13:21:56 +01:00
|
|
|
r.on('end', async () => {
|
2020-11-16 17:19:36 +01:00
|
|
|
try {
|
2020-11-17 13:35:27 +01:00
|
|
|
const releases = JSON.parse(data);
|
2020-11-16 16:55:04 +01:00
|
|
|
|
2020-11-16 17:19:36 +01:00
|
|
|
if (file) {
|
2020-11-17 13:35:27 +01:00
|
|
|
for (const release of releases) {
|
|
|
|
for (const asset of release.assets) {
|
|
|
|
if (asset.name === file) {
|
2020-11-20 13:21:56 +01:00
|
|
|
log.debug('Download', asset.browser_download_url);
|
|
|
|
return await this.download(req, res, next, {
|
|
|
|
repo: {
|
|
|
|
owner: owner,
|
|
|
|
name: name,
|
|
|
|
},
|
|
|
|
asset: {
|
|
|
|
id: asset.id,
|
|
|
|
name: asset.name,
|
|
|
|
url: asset.browser_download_url,
|
|
|
|
size: asset.size,
|
|
|
|
},
|
|
|
|
});
|
2020-11-17 13:35:27 +01:00
|
|
|
}
|
2020-11-16 17:19:36 +01:00
|
|
|
}
|
2020-11-16 16:55:04 +01:00
|
|
|
}
|
2020-11-17 13:35:27 +01:00
|
|
|
throw new NotFoundHttpError('Asset', req.url);
|
2020-11-16 17:19:36 +01:00
|
|
|
} else {
|
|
|
|
log.debug('List files');
|
2020-11-17 13:35:27 +01:00
|
|
|
return res.render('list-files', {
|
|
|
|
owner: owner,
|
|
|
|
name: name,
|
|
|
|
releases: releases,
|
|
|
|
});
|
2020-11-16 16:55:04 +01:00
|
|
|
}
|
2020-11-16 17:19:36 +01:00
|
|
|
} catch (e) {
|
|
|
|
return next(e);
|
2020-11-16 16:55:04 +01:00
|
|
|
}
|
|
|
|
});
|
2020-11-20 13:21:56 +01:00
|
|
|
});
|
|
|
|
httpRequest.on('error', err => {
|
2020-11-16 16:55:04 +01:00
|
|
|
log.error(err);
|
|
|
|
});
|
2020-11-20 13:21:56 +01:00
|
|
|
httpRequest.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected async download(
|
|
|
|
req: Request,
|
|
|
|
res: Response,
|
|
|
|
next: NextFunction,
|
|
|
|
downloadProperties: DownloadProperties,
|
|
|
|
): Promise<void> {
|
|
|
|
// Make base dir
|
|
|
|
if (!await promisify(fs.exists)(ASSETS_BASE_DIR)) {
|
|
|
|
await promisify(fs.mkdir)(ASSETS_BASE_DIR);
|
|
|
|
}
|
|
|
|
|
|
|
|
const assetPath = path.resolve(ASSETS_BASE_DIR, '' + downloadProperties.asset.id);
|
2020-11-20 13:38:32 +01:00
|
|
|
const tmpAssetPath = assetPath + '.tmp';
|
2020-11-20 13:21:56 +01:00
|
|
|
|
|
|
|
// Download asset if it doesn't exist
|
|
|
|
if (!await promisify(fs.exists)(assetPath)) {
|
2020-11-20 13:38:32 +01:00
|
|
|
if (await promisify(fs.exists)(tmpAssetPath)) {
|
|
|
|
throw new ServiceUnavailableHttpError('This file is currently being cached. Please try again later.');
|
|
|
|
}
|
|
|
|
|
|
|
|
const file = fs.createWriteStream(tmpAssetPath);
|
2020-11-20 13:21:56 +01:00
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
const httpRequest = https.get(downloadProperties.asset.url, res => {
|
|
|
|
res.on('end', () => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
res.pipe(file);
|
|
|
|
});
|
|
|
|
httpRequest.on('error', () => {
|
|
|
|
reject();
|
|
|
|
});
|
|
|
|
httpRequest.end();
|
|
|
|
});
|
|
|
|
file.close();
|
2020-11-20 13:38:32 +01:00
|
|
|
await promisify(fs.rename)(tmpAssetPath, assetPath);
|
2020-11-20 13:21:56 +01:00
|
|
|
}
|
|
|
|
|
2020-11-20 13:38:47 +01:00
|
|
|
const options: Record<string, unknown> = {};
|
|
|
|
|
|
|
|
if (req.header('Range')) {
|
|
|
|
options.headers = {
|
|
|
|
'Content-Type': 'multipart/byteranges',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-11-20 13:21:56 +01:00
|
|
|
// Respond
|
2020-11-20 13:38:47 +01:00
|
|
|
return res.download(assetPath, downloadProperties.asset.name, options);
|
2020-11-16 16:55:04 +01:00
|
|
|
}
|
|
|
|
}
|
2020-11-20 13:21:56 +01:00
|
|
|
|
|
|
|
export type DownloadProperties = {
|
|
|
|
repo: {
|
|
|
|
owner: string;
|
|
|
|
name: string;
|
|
|
|
};
|
|
|
|
asset: {
|
|
|
|
id: string,
|
|
|
|
name: string;
|
|
|
|
url: string;
|
|
|
|
size: number;
|
|
|
|
};
|
|
|
|
};
|