diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fb1e80..5c0aed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to MakeMKV Auto Rip will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- **Windows MakeMKV detection** - Detection, validation and the executable the app runs now all use `makemkvcon64.exe`, falling back to `makemkvcon.exe` when the 64-bit executable is not installed + ## [1.0.0] - 2025-08-12 ### Added diff --git a/src/config/index.js b/src/config/index.js index 9769fdd..57cf7dd 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,5 +1,5 @@ import { readFileSync } from "fs"; -import { dirname, join, resolve, normalize, sep } from "path"; +import { dirname, resolve, normalize, sep } from "path"; import { fileURLToPath } from "url"; import { parse } from "yaml"; import { FileSystemUtils } from "../utils/filesystem.js"; @@ -168,10 +168,8 @@ export class AppConfig { const mkvDir = await this.getMkvDir(); if (!mkvDir) return null; - // Handle cross-platform executable names - const executableName = - process.platform === "win32" ? "makemkvcon64.exe" : "makemkvcon"; - const executablePath = join(mkvDir, executableName); + const executablePath = await FileSystemUtils.findMakeMKVExecutable(mkvDir); + if (!executablePath) return null; // Quote the path if it contains spaces (important for Windows paths) return executablePath.includes(" ") diff --git a/src/utils/filesystem.js b/src/utils/filesystem.js index a3204a8..b9733d9 100644 --- a/src/utils/filesystem.js +++ b/src/utils/filesystem.js @@ -120,17 +120,9 @@ export class FileSystemUtils { await access(basePath); // Check if makemkvcon executable exists in this path - const executableName = - platform === "win32" ? "makemkvcon.exe" : "makemkvcon"; - const executablePath = join(basePath, executableName); - - try { - await access(executablePath); + if (await this.findMakeMKVExecutable(basePath)) { Logger.info(`Found MakeMKV installation at: ${basePath}`); return basePath; - } catch { - // Executable not found in this directory, try next - continue; } } catch { // Directory doesn't exist, try next @@ -151,17 +143,30 @@ export class FileSystemUtils { */ static async validateMakeMKVInstallation(mkvDir) { if (!mkvDir) return false; + return Boolean(await this.findMakeMKVExecutable(mkvDir)); + } - try { - const platform = os.platform(); - const executableName = - platform === "win32" ? "makemkvcon.exe" : "makemkvcon"; - const executablePath = join(mkvDir, executableName); + /** + * Find the makemkvcon executable in a MakeMKV directory. + * On Windows, prefers makemkvcon64.exe and falls back to makemkvcon.exe. + * @param {string} mkvDir - Path to MakeMKV directory + * @returns {Promise} - Full executable path or null if not found + */ + static async findMakeMKVExecutable(mkvDir) { + const executableNames = + os.platform() === "win32" + ? ["makemkvcon64.exe", "makemkvcon.exe"] + : ["makemkvcon"]; - await access(executablePath); - return true; - } catch { - return false; + for (const executableName of executableNames) { + const executablePath = join(mkvDir, executableName); + try { + await access(executablePath); + return executablePath; + } catch { + // Not found, try next name + } } + return null; } } diff --git a/tests/unit/config.test.js b/tests/unit/config.test.js index bd14da1..76b62d6 100644 --- a/tests/unit/config.test.js +++ b/tests/unit/config.test.js @@ -3,12 +3,19 @@ */ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; +import { join } from "path"; // Partially mock FileSystemUtils for detection/validation only const mockFileSystemUtils = { detectMakeMKVInstallation: vi.fn().mockResolvedValue("/usr/bin"), validateMakeMKVInstallation: vi.fn().mockResolvedValue(true), + findMakeMKVExecutable: vi.fn(), }; +const platformExecutable = async (mkvDir) => + join( + mkvDir, + process.platform === "win32" ? "makemkvcon64.exe" : "makemkvcon" + ); vi.mock("../../src/utils/filesystem.js", async () => { const actual = await vi.importActual("../../src/utils/filesystem.js"); return { @@ -33,6 +40,9 @@ describe("AppConfig", () => { // Reset mock implementation mockFileSystemUtils.detectMakeMKVInstallation.mockResolvedValue("/usr/bin"); mockFileSystemUtils.validateMakeMKVInstallation.mockResolvedValue(true); + mockFileSystemUtils.findMakeMKVExecutable.mockImplementation( + platformExecutable + ); }); afterEach(() => { diff --git a/tests/unit/filesystem.test.js b/tests/unit/filesystem.test.js index 653ebff..5f032f5 100644 --- a/tests/unit/filesystem.test.js +++ b/tests/unit/filesystem.test.js @@ -80,6 +80,9 @@ describe("FileSystemUtils", () => { const result = await FileSystemUtils.detectMakeMKVInstallation(); expect(result).toBe("C:/Program Files/MakeMKV"); expect(mockedFsPromises.access).toHaveBeenCalled(); + expect(mockedFsPromises.access).toHaveBeenLastCalledWith( + expect.stringMatching(/makemkvcon64\.exe$/) + ); }); it("should detect MakeMKV on Linux", async () => { @@ -125,7 +128,21 @@ describe("FileSystemUtils", () => { "C:/Program Files/MakeMKV" ); expect(result).toBe(true); - expect(mockedFsPromises.access).toHaveBeenCalled(); + expect(mockedFsPromises.access).toHaveBeenCalledWith( + expect.stringMatching(/makemkvcon64\.exe$/) + ); + }); + + it("should fall back to makemkvcon.exe on Windows", async () => { + mockedOs.platform.mockReturnValue("win32"); + mockedFsPromises.access.mockImplementation(async (path) => { + if (path.endsWith("makemkvcon64.exe")) throw new Error("ENOENT"); + }); + + const result = await FileSystemUtils.findMakeMKVExecutable( + "C:/Program Files/MakeMKV" + ); + expect(result).toMatch(/[\\/]makemkvcon\.exe$/); }); it("should validate Unix installation", async () => {