Replace AdmZip with node-stream-zip.

This commit is contained in:
Daniel Scalzi
2020-06-02 23:07:59 -04:00
parent 0b063e4bfc
commit d627ce72a3
12 changed files with 283 additions and 215 deletions

View File

@@ -39,10 +39,10 @@ export class MiscFileStructure extends ModuleStructure {
return acc
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
protected async getModuleId(name: string, path: string): Promise<string> {
return name
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
protected async getModuleName(name: string, path: string): Promise<string> {
return name
}
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {

View File

@@ -1,5 +1,4 @@
import AdmZip from 'adm-zip'
import { Stats } from 'fs-extra'
import StreamZip from 'node-stream-zip'
import toml from 'toml'
import { capitalize } from '../../../../../util/stringutils'
import { VersionUtil } from '../../../../../util/versionutil'
@@ -33,59 +32,90 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
return ForgeModStructure113.isForVersion(version, libraryVersion)
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
const fmData = this.getForgeModMetadata(buf, name)
protected async getModuleId(name: string, path: string): Promise<string> {
const fmData = await this.getForgeModMetadata(name, path)
return this.generateMavenIdentifier(fmData.mods[0].modId, fmData.mods[0].version)
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize((this.getForgeModMetadata(buf, name)).mods[0].displayName)
protected async getModuleName(name: string, path: string): Promise<string> {
return capitalize((await this.getForgeModMetadata(name, path)).mods[0].displayName)
}
private getForgeModMetadata(buf: Buffer, name: string): ModsToml {
if (!Object.prototype.hasOwnProperty.call(this.forgeModMetadata, name)) {
const zip = new AdmZip(buf)
const zipEntries = zip.getEntries()
private getForgeModMetadata(name: string, path: string): Promise<ModsToml> {
return new Promise((resolve, reject) => {
if (!Object.prototype.hasOwnProperty.call(this.forgeModMetadata, name)) {
// Optifine is a tweak that can be loaded as a forge mod. It does not
// appear to contain a mcmod.info class. This a special case we will
// account for.
if (name.toLowerCase().indexOf('optifine') > -1) {
// Read zip for changelog.txt
let rawChangelog
for (const entry of zipEntries) {
if (entry.entryName === 'changelog.txt') {
rawChangelog = zip.readAsText(entry)
break
const zip = new StreamZip({
file: path,
storeEntries: true
})
zip.on('error', err => reject(err))
zip.on('ready', () => {
try {
const res = this.processZip(zip, name)
zip.close()
resolve(res)
return
} catch(err) {
zip.close()
throw err
}
}
if (!rawChangelog) {
throw new Error('Failed to read OptiFine changelog.')
}
const info = rawChangelog.split('\n')[0].trim()
const version = info.split(' ')[1]
this.forgeModMetadata[name] = ({
modid: 'optifine',
name: info,
version,
mcversion: version.substring(0, version.indexOf('_'))
}) as unknown as ModsToml
return this.forgeModMetadata[name] as ModsToml
})
} else {
resolve(this.forgeModMetadata[name] as ModsToml)
return
}
const raw = zip.readAsText('META-INF/mods.toml')
})
}
let createDefault = false
private processZip(zip: StreamZip, name: string): ModsToml {
if (raw) {
// Assuming the main mod will be the first entry in this file.
try {
const parsed = toml.parse(raw) as ModsToml
// Optifine is a tweak that can be loaded as a forge mod. It does not
// appear to contain a mcmod.info class. This a special case we will
// account for.
if (name.toLowerCase().indexOf('optifine') > -1) {
// tslint:disable-next-line: no-invalid-template-strings
if (parsed.mods[0].version === '${file.jarVersion}') {
let version = '0.0.0'
const manifest = zip.readAsText('META-INF/MANIFEST.MF')
const keys = manifest.split('\n')
// Read zip for changelog.txt
let changelogBuf: Buffer
try {
changelogBuf = zip.entryDataSync('changelog.txt')
} catch(err) {
throw new Error('Failed to read OptiFine changelog.')
}
const info = changelogBuf.toString().split('\n')[0].trim()
const version = info.split(' ')[1]
this.forgeModMetadata[name] = ({
modid: 'optifine',
name: info,
version,
mcversion: version.substring(0, version.indexOf('_'))
}) as unknown as ModsToml
return this.forgeModMetadata[name] as ModsToml
}
let raw: Buffer | undefined
try {
raw = zip.entryDataSync('META-INF/mods.toml')
} catch(err) {
// ignored
}
let createDefault = false
if (raw) {
// Assuming the main mod will be the first entry in this file.
try {
const parsed = toml.parse(raw.toString()) as ModsToml
// tslint:disable-next-line: no-invalid-template-strings
if (parsed.mods[0].version === '${file.jarVersion}') {
let version = '0.0.0'
try {
const manifest = zip.entryDataSync('META-INF/MANIFEST.MF')
const keys = manifest.toString().split('\n')
ForgeModStructure113.logger.debug(keys)
for (const key of keys) {
const match = ForgeModStructure113.IMPLEMENTATION_VERSION_REGEX.exec(key)
@@ -94,36 +124,37 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
}
}
ForgeModStructure113.logger.debug(`ForgeMod ${name} contains a version wildcard, inferring ${version}`)
parsed.mods[0].version = version
} catch {
ForgeModStructure113.logger.debug(`ForgeMod ${name} contains a version wildcard yet no MANIFEST.MF.. Defaulting to ${version}`)
}
this.forgeModMetadata[name] = parsed
} catch (err) {
ForgeModStructure113.logger.error(`ForgeMod ${name} contains an invalid mods.toml file.`)
createDefault = true
parsed.mods[0].version = version
}
} else {
ForgeModStructure113.logger.error(`ForgeMod ${name} does not contain mods.toml file.`)
this.forgeModMetadata[name] = parsed
} catch (err) {
ForgeModStructure113.logger.error(`ForgeMod ${name} contains an invalid mods.toml file.`)
createDefault = true
}
} else {
ForgeModStructure113.logger.error(`ForgeMod ${name} does not contain mods.toml file.`)
createDefault = true
}
if (createDefault) {
this.forgeModMetadata[name] = ({
modLoader: 'javafml',
loaderVersion: '',
mods: [{
modId: name.substring(0, name.lastIndexOf('.')).toLowerCase(),
version: '0.0.0',
displayName: name,
description: ''
}]
})
}
if (createDefault) {
this.forgeModMetadata[name] = ({
modLoader: 'javafml',
loaderVersion: '',
mods: [{
modId: name.substring(0, name.lastIndexOf('.')).toLowerCase(),
version: '0.0.0',
displayName: name,
description: ''
}]
})
}
return this.forgeModMetadata[name] as ModsToml
}
}

View File

@@ -1,5 +1,4 @@
import AdmZip from 'adm-zip'
import { Stats } from 'fs-extra'
import StreamZip from 'node-stream-zip'
import { capitalize } from '../../../../../util/stringutils'
import { VersionUtil } from '../../../../../util/versionutil'
import { McModInfo } from '../../../../forge/mcmodinfo'
@@ -31,85 +30,107 @@ export class ForgeModStructure17 extends BaseForgeModStructure {
return ForgeModStructure17.isForVersion(version, libraryVersion)
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
const fmData = this.getForgeModMetadata(buf, name)
protected async getModuleId(name: string, path: string): Promise<string> {
const fmData = await this.getForgeModMetadata(name, path)
return this.generateMavenIdentifier(fmData.modid, fmData.version)
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize((this.getForgeModMetadata(buf, name)).name)
protected async getModuleName(name: string, path: string): Promise<string> {
return capitalize((await this.getForgeModMetadata(name, path)).name)
}
private getForgeModMetadata(buf: Buffer, name: string): McModInfo {
if (!Object.prototype.hasOwnProperty.call(this.forgeModMetadata, name)) {
const zip = new AdmZip(buf)
const zipEntries = zip.getEntries()
private getForgeModMetadata(name: string, path: string): Promise<McModInfo> {
return new Promise((resolve, reject) => {
if (!Object.prototype.hasOwnProperty.call(this.forgeModMetadata, name)) {
// Optifine is a tweak that can be loaded as a forge mod. It does not
// appear to contain a mcmod.info class. This a special case we will
// account for.
if (name.toLowerCase().indexOf('optifine') > -1) {
// Read zip for changelog.txt
let rawChangelog
for (const entry of zipEntries) {
if (entry.entryName === 'changelog.txt') {
rawChangelog = zip.readAsText(entry)
break
const zip = new StreamZip({
file: path,
storeEntries: true
})
zip.on('error', err => reject(err))
zip.on('ready', () => {
try {
const res = this.processZip(zip, name)
zip.close()
resolve(res)
return
} catch(err) {
zip.close()
throw err
}
}
if (!rawChangelog) {
throw new Error('Failed to read OptiFine changelog.')
}
const info = rawChangelog.split('\n')[0].trim()
const version = info.split(' ')[1]
this.forgeModMetadata[name] = ({
modid: 'optifine',
name: info,
version,
mcversion: version.substring(0, version.indexOf('_'))
}) as unknown as McModInfo
return this.forgeModMetadata[name] as McModInfo
}
})
let raw
for (const entry of zipEntries) {
if (entry.entryName === 'mcmod.info') {
raw = zip.readAsText(entry)
break
}
}
let createDefault = false
if (raw) {
// Assuming the main mod will be the first entry in this file.
try {
const resolved = JSON.parse(raw) as (McModInfoList | McModInfo[])
if (Object.prototype.hasOwnProperty.call(resolved, 'modListVersion')) {
this.forgeModMetadata[name] = (resolved as McModInfoList).modList[0]
} else {
this.forgeModMetadata[name] = (resolved as McModInfo[])[0]
}
// No way to resolve this AFAIK
if(this.forgeModMetadata[name]!.version.indexOf('@') > -1 || this.forgeModMetadata[name]!.version.indexOf('$') > -1) {
// Ex. @VERSION@, ${version}
this.forgeModMetadata[name]!.version = '0.0.0'
}
} catch (err) {
ForgeModStructure17.logger.error(`ForgeMod ${name} contains an invalid mcmod.info file.`)
createDefault = true
}
} else {
ForgeModStructure17.logger.error(`ForgeMod ${name} does not contain mcmod.info file.`)
resolve(this.forgeModMetadata[name] as McModInfo)
return
}
})
}
private processZip(zip: StreamZip, name: string): McModInfo {
// Optifine is a tweak that can be loaded as a forge mod. It does not
// appear to contain a mcmod.info class. This a special case we will
// account for.
if (name.toLowerCase().indexOf('optifine') > -1) {
// Read zip for changelog.txt
let changelogBuf: Buffer
try {
changelogBuf = zip.entryDataSync('changelog.txt')
} catch(err) {
throw new Error('Failed to read OptiFine changelog.')
}
const info = changelogBuf.toString().split('\n')[0].trim()
const version = info.split(' ')[1]
this.forgeModMetadata[name] = ({
modid: 'optifine',
name: info,
version,
mcversion: version.substring(0, version.indexOf('_'))
}) as unknown as McModInfo
return this.forgeModMetadata[name] as McModInfo
}
let raw: Buffer | undefined
try {
raw = zip.entryDataSync('mcmod.info')
} catch(err) {
// ignored
}
let createDefault = false
if (raw) {
// Assuming the main mod will be the first entry in this file.
try {
const resolved = JSON.parse(raw.toString()) as (McModInfoList | McModInfo[])
if (Object.prototype.hasOwnProperty.call(resolved, 'modListVersion')) {
this.forgeModMetadata[name] = (resolved as McModInfoList).modList[0]
} else {
this.forgeModMetadata[name] = (resolved as McModInfo[])[0]
}
// No way to resolve this AFAIK
if(this.forgeModMetadata[name]!.version.indexOf('@') > -1 || this.forgeModMetadata[name]!.version.indexOf('$') > -1) {
// Ex. @VERSION@, ${version}
this.forgeModMetadata[name]!.version = '0.0.0'
}
} catch (err) {
ForgeModStructure17.logger.error(`ForgeMod ${name} contains an invalid mcmod.info file.`)
createDefault = true
}
} else {
ForgeModStructure17.logger.error(`ForgeMod ${name} does not contain mcmod.info file.`)
createDefault = true
}
if (createDefault) {
this.forgeModMetadata[name] = ({
modid: name.substring(0, name.lastIndexOf('.')).toLowerCase(),
name,
version: '0.0.0'
}) as unknown as McModInfo
}
if (createDefault) {
this.forgeModMetadata[name] = ({
modid: name.substring(0, name.lastIndexOf('.')).toLowerCase(),
name,
version: '0.0.0'
}) as unknown as McModInfo
}
return this.forgeModMetadata[name] as McModInfo

View File

@@ -34,12 +34,12 @@ export class LibraryStructure extends ModuleStructure {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
protected async getModuleId(name: string, path: string): Promise<string> {
const inference = this.attemptCrudeInference(name)
return this.generateMavenIdentifier(inference.name, inference.version)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
protected async getModuleName(name: string, path: string): Promise<string> {
const inference = this.attemptCrudeInference(name)
return inference.name
}

View File

@@ -1,4 +1,4 @@
import AdmZip from 'adm-zip'
import StreamZip from 'node-stream-zip'
import { Stats } from 'fs-extra'
import { Type } from 'helios-distribution-types'
import { join } from 'path'
@@ -19,12 +19,12 @@ export class LiteModStructure extends ModuleStructure {
super(absoluteRoot, relativeRoot, 'litemods', baseUrl, Type.LiteMod)
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
const liteModData = this.getLiteModMetadata(buf, name)
protected async getModuleId(name: string, path: string): Promise<string> {
const liteModData = await this.getLiteModMetadata(name, path)
return this.generateMavenIdentifier(liteModData.name, `${liteModData.version}-${liteModData.mcversion}`)
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize(this.getLiteModMetadata(buf, name).name)
protected async getModuleName(name: string, path: string): Promise<string> {
return capitalize((await this.getLiteModMetadata(name, path)).name)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
@@ -35,27 +35,53 @@ export class LiteModStructure extends ModuleStructure {
return null
}
private getLiteModMetadata(buf: Buffer, name: string): LiteMod {
if (!Object.prototype.hasOwnProperty.call(this.liteModMetadata, name)) {
const zip = new AdmZip(buf)
const zipEntries = zip.getEntries()
private getLiteModMetadata(name: string, path: string): Promise<LiteMod> {
return new Promise((resolve, reject) => {
if (!Object.prototype.hasOwnProperty.call(this.liteModMetadata, name)) {
let raw
for (const entry of zipEntries) {
if (entry.entryName === 'litemod.json') {
raw = zip.readAsText(entry)
break
}
}
const zip = new StreamZip({
file: path,
storeEntries: true
})
zip.on('error', err => reject(err))
zip.on('ready', () => {
try {
const res = this.processZip(zip, name)
zip.close()
resolve(res)
return
} catch(err) {
zip.close()
throw err
}
})
if (raw) {
this.liteModMetadata[name] = JSON.parse(raw) as LiteMod
} else {
throw new Error(`Litemod ${name} does not contain litemod.json file.`)
resolve(this.liteModMetadata[name] as LiteMod)
return
}
})
}
private processZip(zip: StreamZip, name: string): LiteMod {
let raw: Buffer | undefined
try {
raw = zip.entryDataSync('litemod.json')
} catch(err) {
// ignored
}
if (raw) {
this.liteModMetadata[name] = JSON.parse(raw.toString()) as LiteMod
} else {
throw new Error(`Litemod ${name} does not contain litemod.json file.`)
}
return this.liteModMetadata[name] as LiteMod
}
}

View File

@@ -29,16 +29,16 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
return `generated.${this.type.toLowerCase()}:${name}:${version}@${TypeMetadata[this.type].defaultExtension}`
}
protected async abstract getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
protected async abstract getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
protected async abstract getModuleId(name: string, path: string): Promise<string>
protected async abstract getModuleName(name: string, path: string): Promise<string>
protected async abstract getModuleUrl(name: string, path: string, stats: Stats): Promise<string>
protected async abstract getModulePath(name: string, path: string, stats: Stats): Promise<string | null>
protected async parseModule(file: string, filePath: string, stats: Stats): Promise<Module> {
const buf = await readFile(filePath)
const mdl: Module = {
id: await this.getModuleId(file, filePath, stats, buf),
name: await this.getModuleName(file, filePath, stats, buf),
id: await this.getModuleId(file, filePath),
name: await this.getModuleName(file, filePath),
type: this.type,
required: {
value: false,