diff --git a/packages/host/host.ts b/packages/host/host.ts index 19127713..6f9205ee 100755 --- a/packages/host/host.ts +++ b/packages/host/host.ts @@ -25,6 +25,7 @@ import * as lib from "@clusterio/lib"; import { ConsoleTransport, levels, logger } from "@clusterio/lib"; import Host from "./src/Host"; +import { getFactorioVersion } from "./src/server"; let host: Host | undefined; @@ -236,6 +237,31 @@ async function startHost() { tlsCa = await fs.readFile(tlsCaPath, "utf8"); } + // Migrate from single factorio version to multiple + const factorioDir = hostConfig.get("host.factorio_directory"); + const singleFactorioVersion = await getFactorioVersion(factorioDir); + if (singleFactorioVersion !== null) { + const dst = path.join(factorioDir, singleFactorioVersion); + logger.info(`Found single factorio version in ${factorioDir} moving to ${dst}`); + const stats = await fs.lstat(factorioDir); + if (stats.isSymbolicLink()) { + // For symlinks we want to move the file, but cant move it into itself so need a tmp move + const tmpPath = `${factorioDir}.tmp`; + await fs.move(factorioDir, tmpPath); + await fs.ensureDir(factorioDir); + await fs.move(tmpPath, dst); + } else { + // It is a directory here, otherwise no version would have been found + // We will only move known files in case a version was incorrectly installed in the dir + await fs.ensureDir(dst); + await Promise.all( + ["bin", "data", "config-path.cfg"].map(name => ( + fs.move(path.join(factorioDir, name), path.join(dst, name)) + )) + ); + } + } + let hostConnector = new HostConnector(hostConfig, tlsCa, pluginInfos); host = new Host( hostConnector, diff --git a/packages/host/src/server.ts b/packages/host/src/server.ts index dc45d101..89aacb05 100644 --- a/packages/host/src/server.ts +++ b/packages/host/src/server.ts @@ -14,11 +14,11 @@ import * as lib from "@clusterio/lib"; * Determines the version of Factorio the datadir is pointing to by * reading the changelog.txt in it. * - * @param changelogPath - Path to changelog.txt. - * @internal + * @param factorioDir - Path factorio dir containing data/changelog.txt. */ -async function getVersion(changelogPath: string) { +export async function getFactorioVersion(factorioDir: string) { let changelog; + const changelogPath = path.join(factorioDir, "data", "changelog.txt"); try { changelog = await fs.readFile(changelogPath, "utf-8"); } catch (err: any) { @@ -28,10 +28,10 @@ async function getVersion(changelogPath: string) { throw err; } - for (let line of changelog.split(/[\r\n]+/)) { - let index = line.indexOf(":"); + for (const line of changelog.split(/[\r\n]+/)) { + const index = line.indexOf(":"); if (index !== -1) { - let nameĀ = line.slice(0, index).trim(); + const name = line.slice(0, index).trim(); if (name.toLowerCase() === "version") { return line.slice(index + 1).trim(); } @@ -80,40 +80,29 @@ function versionOrder(a: string, b: string) { * @internal */ async function findVersion(factorioDir: string, targetVersion: lib.TargetVersion): Promise<[string, lib.FullVersion]> { - - // There are two supported setups: having the factorio dir be the actual - // install directory, and having the factorio dir be a folder containing - // multiple install directories - - let simpleVersion = await getVersion(path.join(factorioDir, "data", "changelog.txt")); - if (simpleVersion !== null) { - if (simpleVersion === targetVersion || simpleVersion.startsWith(targetVersion) || targetVersion === "latest") { - if (lib.isFullVersion(simpleVersion)) { - return [path.join(factorioDir, "data"), simpleVersion]; + const versions = new Map(); + for (const name of await fs.readdir(factorioDir)) { + // We do not use withFileTypes or lstat because we want to follow symlinks + const fullPath = path.join(factorioDir, name); + try { + const stats = await fs.stat(fullPath); + if (!stats.isDirectory()) { + continue; } - } - - throw new Error( - `Factorio version ${targetVersion} was requested, but install directory contains ${simpleVersion}` - ); - } - - let versions = new Map(); - for (let entry of await fs.readdir(factorioDir, { withFileTypes: true })) { - if (!entry.isDirectory()) { + } catch { continue; } - let version = await getVersion(path.join(factorioDir, entry.name, "data", "changelog.txt")); + const version = await getFactorioVersion(fullPath); if (version === null || !lib.isFullVersion(version)) { continue; } if (version === targetVersion) { - return [path.join(factorioDir, entry.name, "data"), version]; + return [path.join(factorioDir, name, "data"), version]; } - versions.set(version, entry.name); + versions.set(version, name); } if (!versions.size) { @@ -1421,7 +1410,6 @@ export class FactorioServer extends events.EventEmitter { // For testing only -export const _getVersion = getVersion; export const _versionOrder = versionOrder; export const _findVersion = findVersion; export const _randomDynamicPort = randomDynamicPort; diff --git a/test/file/changelog-bad.txt b/test/file/changelogs/bad/data/changelog.txt similarity index 100% rename from test/file/changelog-bad.txt rename to test/file/changelogs/bad/data/changelog.txt diff --git a/test/file/changelog-test.txt b/test/file/changelogs/good/data/changelog.txt similarity index 100% rename from test/file/changelog-test.txt rename to test/file/changelogs/good/data/changelog.txt diff --git a/test/file/factorio/data/base/locale/en/base.cfg b/test/file/factorio/0.1.1/data/base/locale/en/base.cfg similarity index 100% rename from test/file/factorio/data/base/locale/en/base.cfg rename to test/file/factorio/0.1.1/data/base/locale/en/base.cfg diff --git a/test/file/factorio/data/changelog.txt b/test/file/factorio/0.1.1/data/changelog.txt similarity index 100% rename from test/file/factorio/data/changelog.txt rename to test/file/factorio/0.1.1/data/changelog.txt diff --git a/test/file/factorio/data/core/locale/en/core.cfg b/test/file/factorio/0.1.1/data/core/locale/en/core.cfg similarity index 100% rename from test/file/factorio/data/core/locale/en/core.cfg rename to test/file/factorio/0.1.1/data/core/locale/en/core.cfg diff --git a/test/file/factorio/not-a-dir b/test/file/factorio/not-a-dir new file mode 100644 index 00000000..e69de29b diff --git a/test/file/factorio/not-an-install/not-an-install b/test/file/factorio/not-an-install/not-an-install new file mode 100644 index 00000000..e69de29b diff --git a/test/host/server.js b/test/host/server.js index 1dc541cd..42e32cde 100644 --- a/test/host/server.js +++ b/test/host/server.js @@ -11,17 +11,17 @@ const { testLines } = require("../lib/factorio/lines"); describe("host/server", function() { - describe("_getVersion()", function() { + describe("getFactorioVersion()", function() { it("should get the version from a changelog", async function() { - let version = await hostServer._getVersion(path.join("test", "file", "changelog-test.txt")); + let version = await hostServer.getFactorioVersion(path.join("test", "file", "changelogs", "good")); assert.equal(version, "0.1.1"); }); it("should return null if unable to find the version", async function() { - let version = await hostServer._getVersion(path.join("test", "file", "changelog-bad.txt")); + let version = await hostServer.getFactorioVersion(path.join("test", "file", "changelogs", "bad")); assert.equal(version, null); }); it("should return null if file does not exist", async function() { - let version = await hostServer._getVersion(path.join("test", "file", "does-not-exist.txt")); + let version = await hostServer.getFactorioVersion(path.join("test", "file", "changelogs", "not-exists")); assert.equal(version, null); }); }); @@ -38,46 +38,33 @@ describe("host/server", function() { }); describe("_findVersion()", function() { - it("should find a given install dir with latest as target", async function() { - let installDir = path.join("test", "file", "factorio"); - let [dir, version] = await hostServer._findVersion(installDir, "latest"); - assert.equal(dir, path.join(installDir, "data")); - assert.equal(version, "0.1.1"); - }); - it("should find a given install dir with correct version as target", async function() { - let installDir = path.join("test", "file", "factorio"); - let [dir, version] = await hostServer._findVersion(installDir, "0.1.1"); - assert.equal(dir, path.join(installDir, "data")); - assert.equal(version, "0.1.1"); - }); - it("should reject if the install dir version does not match target version", async function() { - let installDir = path.join("test", "file", "factorio"); - await assert.rejects( - hostServer._findVersion(installDir, "0.1.2"), - new Error("Factorio version 0.1.2 was requested, but install directory contains 0.1.1") - ); - }); it("should search given directory for latest Factorio install", async function() { - let installDir = path.join("test", "file"); - let [dir, version] = await hostServer._findVersion(installDir, "latest"); - assert.equal(dir, path.join(installDir, "factorio", "data")); + const installDir = path.join("test", "file", "factorio"); + const [dir, version] = await hostServer._findVersion(installDir, "latest"); + assert.equal(dir, path.join(installDir, "0.1.1", "data")); assert.equal(version, "0.1.1"); }); it("should search given directory for given Factorio install", async function() { - let installDir = path.join("test", "file"); - let [dir, version] = await hostServer._findVersion(installDir, "0.1.1"); - assert.equal(dir, path.join(installDir, "factorio", "data")); + const installDir = path.join("test", "file", "factorio"); + const [dir, version] = await hostServer._findVersion(installDir, "0.1.1"); + assert.equal(dir, path.join(installDir, "0.1.1", "data")); + assert.equal(version, "0.1.1"); + }); + it("should search given directory for partly given Factorio install", async function() { + const installDir = path.join("test", "file", "factorio"); + const [dir, version] = await hostServer._findVersion(installDir, "0.1"); + assert.equal(dir, path.join(installDir, "0.1.1", "data")); assert.equal(version, "0.1.1"); }); it("should reject if no factorio install with the given version was found", async function() { - let installDir = path.join("test", "file"); + let installDir = path.join("test", "file", "factorio"); await assert.rejects( hostServer._findVersion(installDir, "0.1.2"), new Error("Unable to find Factorio version 0.1.2") ); }); it("should reject if no factorio install was found", async function() { - let installDir = path.join("test", "file", "instances"); + let installDir = path.join("test", "file"); await assert.rejects( hostServer._findVersion(installDir, "latest"), new Error(`Unable to find any Factorio install in ${installDir}`) diff --git a/test/integration/server.js b/test/integration/server.js index 1035c16e..f843700d 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -5,14 +5,14 @@ const fs = require("fs-extra"); const path = require("path"); const lib = require("@clusterio/lib"); -const { FactorioServer, _getVersion } = require("@clusterio/host/dist/node/src/server"); +const { FactorioServer, getFactorioVersion } = require("@clusterio/host/dist/node/src/server"); const { logger } = lib; const { slowTest, factorioDir } = require("./index"); describe("Integration of host/src/server", function() { - describe("_getVersion()", function() { + describe("getFactorioVersion()", function() { it("should get a version from factorio's changelog.txt", async function() { function checkVersion(version) { if (!/^\d+\.\d+\.\d+$/.test(version)) { @@ -20,7 +20,7 @@ describe("Integration of host/src/server", function() { } } - let version = await _getVersion(path.join(factorioDir, "data", "changelog.txt")); + let version = await getFactorioVersion(factorioDir); if (version !== null) { checkVersion(version); } else { @@ -30,7 +30,7 @@ describe("Integration of host/src/server", function() { } checkVersion( - await _getVersion(path.join(factorioDir, entry.name, "data", "changelog.txt")) + await getFactorioVersion(path.join(factorioDir, entry.name)) ); } }