mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-24 12:15:27 -07:00
fix(launcher): restore Matroska thumbnails in Linux rofi picker (#210)
This commit is contained in:
@@ -8,6 +8,18 @@ import {
|
||||
resolveManagedLinuxRuntimePluginPaths,
|
||||
} from './linux-runtime-plugin-assets';
|
||||
|
||||
const THUMBNAILER_RELATIVE_PATH = path.join(
|
||||
'thumbnailers',
|
||||
'subminer-ffmpegthumbnailer.thumbnailer',
|
||||
);
|
||||
|
||||
function writeThumbnailer(rootDir: string, content = '[Thumbnailer Entry]\n'): string {
|
||||
const thumbnailerPath = path.join(rootDir, THUMBNAILER_RELATIVE_PATH);
|
||||
fs.mkdirSync(path.dirname(thumbnailerPath), { recursive: true });
|
||||
fs.writeFileSync(thumbnailerPath, content);
|
||||
return thumbnailerPath;
|
||||
}
|
||||
|
||||
async function withTempDir<T>(fn: (dir: string) => Promise<T> | T): Promise<T> {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-linux-plugin-assets-test-'));
|
||||
try {
|
||||
@@ -48,6 +60,7 @@ test('resolveManagedLinuxRuntimePluginPaths resolves XDG data target paths', ()
|
||||
pluginEntrypointPath: '/tmp/xdg-data/SubMiner/plugin/subminer/main.lua',
|
||||
pluginConfigPath: '/tmp/xdg-data/SubMiner/plugin/subminer.conf',
|
||||
themePath: '/tmp/xdg-data/SubMiner/themes/subminer.rasi',
|
||||
thumbnailerPath: '/tmp/xdg-data/SubMiner/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,6 +92,7 @@ test('ensureLinuxRuntimePluginAssets installs managed plugin dir, config, and ro
|
||||
await withTempDir(async (tempDir) => {
|
||||
const sourceRoot = path.join(tempDir, 'source', 'plugin');
|
||||
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
|
||||
const thumbnailerSourcePath = writeThumbnailer(path.join(tempDir, 'source', 'assets'));
|
||||
const targetRoot = path.join(tempDir, 'xdg-data', 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
|
||||
@@ -94,6 +108,7 @@ test('ensureLinuxRuntimePluginAssets installs managed plugin dir, config, and ro
|
||||
pluginDirSource: path.join(sourceRoot, 'subminer'),
|
||||
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
|
||||
themeSourcePath,
|
||||
thumbnailerSourcePath,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -117,6 +132,19 @@ test('ensureLinuxRuntimePluginAssets installs managed plugin dir, config, and ro
|
||||
),
|
||||
'/* theme */\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(
|
||||
path.join(
|
||||
tempDir,
|
||||
'xdg-data',
|
||||
'SubMiner',
|
||||
'thumbnailers',
|
||||
'subminer-ffmpegthumbnailer.thumbnailer',
|
||||
),
|
||||
'utf8',
|
||||
),
|
||||
'[Thumbnailer Entry]\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,6 +152,7 @@ test('ensureLinuxRuntimePluginAssets installs managed theme when plugin assets a
|
||||
await withTempDir(async (tempDir) => {
|
||||
const sourceRoot = path.join(tempDir, 'source', 'plugin');
|
||||
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
|
||||
const thumbnailerSourcePath = writeThumbnailer(path.join(tempDir, 'source', 'assets'));
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
|
||||
@@ -143,6 +172,7 @@ test('ensureLinuxRuntimePluginAssets installs managed theme when plugin assets a
|
||||
pluginDirSource: path.join(sourceRoot, 'subminer'),
|
||||
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
|
||||
themeSourcePath,
|
||||
thumbnailerSourcePath,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -169,6 +199,7 @@ test('ensureLinuxRuntimePluginAssets installs managed theme when plugin assets a
|
||||
test('ensureLinuxRuntimePluginAssets installs managed theme without resolving plugin sources when plugin assets already exist', async () => {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
|
||||
const thumbnailerSourcePath = writeThumbnailer(path.join(tempDir, 'source', 'assets'));
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.dirname(themeSourcePath), { recursive: true });
|
||||
@@ -183,6 +214,7 @@ test('ensureLinuxRuntimePluginAssets installs managed theme without resolving pl
|
||||
xdgDataHome,
|
||||
resolveBundledAssets: () => ({
|
||||
themeSourcePath,
|
||||
thumbnailerSourcePath,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -250,6 +282,7 @@ test('ensureLinuxRuntimePluginAssets installs managed plugin assets without reso
|
||||
path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'),
|
||||
'/* existing theme */\n',
|
||||
);
|
||||
const thumbnailerSourcePath = writeThumbnailer(path.join(tempDir, 'source', 'assets'));
|
||||
|
||||
const result = await ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
@@ -258,6 +291,7 @@ test('ensureLinuxRuntimePluginAssets installs managed plugin assets without reso
|
||||
resolveBundledAssets: () => ({
|
||||
pluginDirSource: path.join(sourceRoot, 'subminer'),
|
||||
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
|
||||
thumbnailerSourcePath,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -332,6 +366,7 @@ test('ensureLinuxRuntimePluginAssets returns already-present when managed assets
|
||||
path.join(xdgDataHome, 'SubMiner', 'themes', 'subminer.rasi'),
|
||||
'/* theme */\n',
|
||||
);
|
||||
writeThumbnailer(path.join(xdgDataHome, 'SubMiner'));
|
||||
|
||||
const result = await ensureLinuxRuntimePluginAssets({
|
||||
platform: 'linux',
|
||||
@@ -369,6 +404,7 @@ test('ensureLinuxRuntimePluginAssets leaves no final target tree on failed insta
|
||||
await withTempDir(async (tempDir) => {
|
||||
const sourceRoot = path.join(tempDir, 'source', 'plugin');
|
||||
const themeSourcePath = path.join(tempDir, 'source', 'assets', 'themes', 'subminer.rasi');
|
||||
const thumbnailerSourcePath = writeThumbnailer(path.join(tempDir, 'source', 'assets'));
|
||||
const xdgDataHome = path.join(tempDir, 'xdg-data');
|
||||
const targetRoot = path.join(xdgDataHome, 'SubMiner', 'plugin');
|
||||
fs.mkdirSync(path.join(sourceRoot, 'subminer'), { recursive: true });
|
||||
@@ -385,6 +421,7 @@ test('ensureLinuxRuntimePluginAssets leaves no final target tree on failed insta
|
||||
pluginDirSource: path.join(sourceRoot, 'subminer'),
|
||||
pluginConfigSource: path.join(sourceRoot, 'subminer.conf'),
|
||||
themeSourcePath,
|
||||
thumbnailerSourcePath,
|
||||
}),
|
||||
copyFile: async () => {
|
||||
throw new Error('copy failed');
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface ManagedLinuxRuntimePluginPaths {
|
||||
pluginEntrypointPath: string;
|
||||
pluginConfigPath: string;
|
||||
themePath: string;
|
||||
thumbnailerPath: string;
|
||||
}
|
||||
|
||||
export interface EnsureLinuxRuntimePluginAssetsResult {
|
||||
@@ -23,6 +24,7 @@ interface RuntimePluginAssetSources {
|
||||
pluginDirSource?: string;
|
||||
pluginConfigSource?: string;
|
||||
themeSourcePath?: string;
|
||||
thumbnailerSourcePath?: string;
|
||||
}
|
||||
|
||||
interface RuntimePluginDirentLike {
|
||||
@@ -72,6 +74,11 @@ export function resolveManagedLinuxRuntimePluginPaths(options: {
|
||||
pluginEntrypointPath: pathModule.join(pluginDir, 'main.lua'),
|
||||
pluginConfigPath: pathModule.join(rootDir, 'subminer.conf'),
|
||||
themePath: pathModule.join(dataDir, 'themes', 'subminer.rasi'),
|
||||
thumbnailerPath: pathModule.join(
|
||||
dataDir,
|
||||
'thumbnailers',
|
||||
'subminer-ffmpegthumbnailer.thumbnailer',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,11 +102,12 @@ async function copyDirectoryRecursive(
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBundledThemePath(options: {
|
||||
function resolveBundledAssetPath(options: {
|
||||
dirname: string;
|
||||
appPath: string;
|
||||
resourcesPath: string;
|
||||
existsSync: (candidate: string) => boolean;
|
||||
relativePath: string;
|
||||
}): string | null {
|
||||
const roots = [
|
||||
path.join(options.resourcesPath, 'assets'),
|
||||
@@ -111,7 +119,7 @@ function resolveBundledThemePath(options: {
|
||||
];
|
||||
|
||||
for (const root of roots) {
|
||||
const candidate = path.join(root, 'themes', 'subminer.rasi');
|
||||
const candidate = path.join(root, options.relativePath);
|
||||
if (options.existsSync(candidate)) return candidate;
|
||||
}
|
||||
|
||||
@@ -129,16 +137,25 @@ function resolveBundledAssetsDefault(
|
||||
existsSync,
|
||||
});
|
||||
|
||||
const themeSourcePath = resolveBundledThemePath({
|
||||
const themeSourcePath = resolveBundledAssetPath({
|
||||
dirname: __dirname,
|
||||
appPath: process.execPath,
|
||||
resourcesPath,
|
||||
existsSync,
|
||||
relativePath: path.join('themes', 'subminer.rasi'),
|
||||
});
|
||||
const thumbnailerSourcePath = resolveBundledAssetPath({
|
||||
dirname: __dirname,
|
||||
appPath: process.execPath,
|
||||
resourcesPath,
|
||||
existsSync,
|
||||
relativePath: path.join('thumbnailers', 'subminer-ffmpegthumbnailer.thumbnailer'),
|
||||
});
|
||||
|
||||
return {
|
||||
...(pluginAssets ?? {}),
|
||||
...(themeSourcePath ? { themeSourcePath } : {}),
|
||||
...(thumbnailerSourcePath ? { thumbnailerSourcePath } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,7 +195,8 @@ export async function ensureLinuxRuntimePluginAssets(
|
||||
const pluginAssetsExist =
|
||||
existsSync(managedPaths.pluginEntrypointPath) && existsSync(managedPaths.pluginConfigPath);
|
||||
const themeExists = existsSync(managedPaths.themePath);
|
||||
if (pluginAssetsExist && themeExists) {
|
||||
const thumbnailerExists = existsSync(managedPaths.thumbnailerPath);
|
||||
if (pluginAssetsExist && themeExists && thumbnailerExists) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 'already-present',
|
||||
@@ -193,6 +211,7 @@ export async function ensureLinuxRuntimePluginAssets(
|
||||
|
||||
const shouldInstallPluginAssets = !pluginAssetsExist;
|
||||
const shouldInstallTheme = !themeExists;
|
||||
const shouldInstallThumbnailer = !thumbnailerExists;
|
||||
if (
|
||||
shouldInstallPluginAssets &&
|
||||
(!bundledAssets.pluginDirSource || !bundledAssets.pluginConfigSource)
|
||||
@@ -210,6 +229,13 @@ export async function ensureLinuxRuntimePluginAssets(
|
||||
error: 'Bundled Linux runtime theme asset was not found.',
|
||||
};
|
||||
}
|
||||
if (shouldInstallThumbnailer && !bundledAssets.thumbnailerSourcePath) {
|
||||
return {
|
||||
ok: false,
|
||||
status: 'failed',
|
||||
error: 'Bundled Linux rofi thumbnailer asset was not found.',
|
||||
};
|
||||
}
|
||||
|
||||
const stagingSuffix = `${process.pid}-${Date.now()}`;
|
||||
const stagedPluginDir = pathModule.join(managedPaths.rootDir, `.subminer-stage-${stagingSuffix}`);
|
||||
@@ -221,9 +247,14 @@ export async function ensureLinuxRuntimePluginAssets(
|
||||
pathModule.dirname(managedPaths.themePath),
|
||||
`.subminer.rasi-stage-${stagingSuffix}`,
|
||||
);
|
||||
const stagedThumbnailerPath = pathModule.join(
|
||||
pathModule.dirname(managedPaths.thumbnailerPath),
|
||||
`.subminer-ffmpegthumbnailer.thumbnailer-stage-${stagingSuffix}`,
|
||||
);
|
||||
let pluginDirInstalled = false;
|
||||
let pluginConfigInstalled = false;
|
||||
let themeInstalled = false;
|
||||
let thumbnailerInstalled = false;
|
||||
|
||||
try {
|
||||
if (shouldInstallPluginAssets) {
|
||||
@@ -249,6 +280,14 @@ export async function ensureLinuxRuntimePluginAssets(
|
||||
await mkdir(pathModule.dirname(managedPaths.themePath), { recursive: true });
|
||||
await copyFile(themeSourcePath, stagedThemePath);
|
||||
}
|
||||
if (shouldInstallThumbnailer) {
|
||||
const thumbnailerSourcePath = bundledAssets.thumbnailerSourcePath;
|
||||
if (!thumbnailerSourcePath) {
|
||||
throw new Error('Bundled Linux rofi thumbnailer asset was not found.');
|
||||
}
|
||||
await mkdir(pathModule.dirname(managedPaths.thumbnailerPath), { recursive: true });
|
||||
await copyFile(thumbnailerSourcePath, stagedThumbnailerPath);
|
||||
}
|
||||
if (shouldInstallPluginAssets) {
|
||||
await rm(managedPaths.pluginDir, { recursive: true, force: true });
|
||||
await rm(managedPaths.pluginConfigPath, { force: true });
|
||||
@@ -262,6 +301,11 @@ export async function ensureLinuxRuntimePluginAssets(
|
||||
await rename(stagedThemePath, managedPaths.themePath);
|
||||
themeInstalled = true;
|
||||
}
|
||||
if (shouldInstallThumbnailer) {
|
||||
await rm(managedPaths.thumbnailerPath, { force: true });
|
||||
await rename(stagedThumbnailerPath, managedPaths.thumbnailerPath);
|
||||
thumbnailerInstalled = true;
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
@@ -278,9 +322,13 @@ export async function ensureLinuxRuntimePluginAssets(
|
||||
if (themeInstalled) {
|
||||
await rm(managedPaths.themePath, { force: true }).catch(() => {});
|
||||
}
|
||||
if (thumbnailerInstalled) {
|
||||
await rm(managedPaths.thumbnailerPath, { force: true }).catch(() => {});
|
||||
}
|
||||
await rm(stagedPluginDir, { recursive: true, force: true }).catch(() => {});
|
||||
await rm(stagedPluginConfigPath, { force: true }).catch(() => {});
|
||||
await rm(stagedThemePath, { force: true }).catch(() => {});
|
||||
await rm(stagedThumbnailerPath, { force: true }).catch(() => {});
|
||||
return {
|
||||
ok: false,
|
||||
status: 'failed',
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from './support-assets';
|
||||
|
||||
type SupportAssetsResultWithComponent = SupportAssetsUpdateResult & {
|
||||
component?: 'theme' | 'plugin';
|
||||
component?: 'theme' | 'thumbnailer' | 'plugin';
|
||||
};
|
||||
|
||||
function sha256(data: Buffer): string {
|
||||
@@ -22,18 +22,30 @@ function sha256(data: Buffer): string {
|
||||
|
||||
function makeSupportAssetsArchive(options?: {
|
||||
themeContent?: string;
|
||||
thumbnailerContent?: string;
|
||||
includeThumbnailer?: boolean;
|
||||
pluginVersion?: string | null;
|
||||
pluginMainContent?: string;
|
||||
extraPluginFiles?: Array<{ relativePath: string; content: string }>;
|
||||
}): { archive: Buffer; tempDir: string } {
|
||||
const themeContent = options?.themeContent ?? 'new theme\n';
|
||||
const thumbnailerContent = options?.thumbnailerContent ?? '[Thumbnailer Entry]\n';
|
||||
const pluginVersion = options && 'pluginVersion' in options ? options.pluginVersion : '0.12.0';
|
||||
const pluginMainContent = options?.pluginMainContent ?? 'new plugin\n';
|
||||
const extraPluginFiles = options?.extraPluginFiles ?? [];
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-support-assets-test-'));
|
||||
fs.mkdirSync(path.join(tempDir, 'assets/themes'), { recursive: true });
|
||||
if (options?.includeThumbnailer !== false) {
|
||||
fs.mkdirSync(path.join(tempDir, 'assets/thumbnailers'), { recursive: true });
|
||||
}
|
||||
fs.mkdirSync(path.join(tempDir, 'plugin/subminer'), { recursive: true });
|
||||
fs.writeFileSync(path.join(tempDir, 'assets/themes/subminer.rasi'), themeContent);
|
||||
if (options?.includeThumbnailer !== false) {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer'),
|
||||
thumbnailerContent,
|
||||
);
|
||||
}
|
||||
fs.writeFileSync(path.join(tempDir, 'plugin/subminer/main.lua'), pluginMainContent);
|
||||
if (pluginVersion !== null) {
|
||||
fs.writeFileSync(
|
||||
@@ -93,7 +105,7 @@ test('detectSupportAssetDataDirs only returns Linux support-asset locations', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('buildProtectedSupportAssetsCommand installs both theme and plugin assets', () => {
|
||||
test('buildProtectedSupportAssetsCommand installs theme, thumbnailer, and plugin assets', () => {
|
||||
const command = buildProtectedSupportAssetsCommand(
|
||||
"https://example.test/subminer assets.tar.gz?sig='abc'",
|
||||
'ABCDEF1234',
|
||||
@@ -110,11 +122,28 @@ test('buildProtectedSupportAssetsCommand installs both theme and plugin assets',
|
||||
command,
|
||||
/printf '%s %s\\n' 'abcdef1234' "\$tmp\/subminer-assets\.tar\.gz" \| sha256sum -c -/,
|
||||
);
|
||||
const requiredAssetChecks = [
|
||||
'test -f "$tmp/assets/themes/subminer.rasi"',
|
||||
'test -f "$tmp/assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer"',
|
||||
'test -f "$tmp/plugin/subminer/main.lua"',
|
||||
'test -f "$tmp/plugin/subminer/version.lua"',
|
||||
];
|
||||
const firstSudoIndex = command.indexOf('sudo ');
|
||||
assert.notEqual(firstSudoIndex, -1);
|
||||
for (const check of requiredAssetChecks) {
|
||||
const checkIndex = command.indexOf(check);
|
||||
assert.notEqual(checkIndex, -1);
|
||||
assert.ok(checkIndex < firstSudoIndex);
|
||||
}
|
||||
assert.match(command, /sudo mkdir -p '\/usr\/local\/share\/SubMiner'\\''s data'\/themes/);
|
||||
assert.match(
|
||||
command,
|
||||
/sudo cp "\$tmp\/assets\/themes\/subminer\.rasi" '\/usr\/local\/share\/SubMiner'\\''s data'\/themes\/subminer\.rasi/,
|
||||
);
|
||||
assert.match(
|
||||
command,
|
||||
/sudo cp "\$tmp\/assets\/thumbnailers\/subminer-ffmpegthumbnailer\.thumbnailer" .*thumbnailers\/subminer-ffmpegthumbnailer\.thumbnailer/,
|
||||
);
|
||||
assert.match(command, /sudo mkdir -p '\/usr\/local\/share\/SubMiner'\\''s data'\/plugin/);
|
||||
assert.match(command, /sudo rm -rf .*plugin\/subminer\.next/);
|
||||
assert.match(command, /sudo cp -R "\$tmp\/plugin\/subminer" .*plugin\/subminer\.next/);
|
||||
@@ -209,6 +238,12 @@ test('updateSupportAssetsFromRelease installs missing plugin into a root with a
|
||||
path: dataDir,
|
||||
message: 'Updated theme.',
|
||||
},
|
||||
{
|
||||
status: 'updated',
|
||||
component: 'thumbnailer',
|
||||
path: dataDir,
|
||||
message: 'Installed rofi thumbnailer.',
|
||||
},
|
||||
{
|
||||
status: 'updated',
|
||||
component: 'plugin',
|
||||
@@ -220,6 +255,13 @@ test('updateSupportAssetsFromRelease installs missing plugin into a root with a
|
||||
fs.readFileSync(path.join(dataDir, 'themes/subminer.rasi'), 'utf8'),
|
||||
'new theme\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(
|
||||
path.join(dataDir, 'thumbnailers/subminer-ffmpegthumbnailer.thumbnailer'),
|
||||
'utf8',
|
||||
),
|
||||
'[Thumbnailer Entry]\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(dataDir, 'plugin/subminer/main.lua'), 'utf8'),
|
||||
'new plugin\n',
|
||||
@@ -345,8 +387,13 @@ test('updateSupportAssetsFromRelease skips identical theme and up-to-date plugin
|
||||
const xdgDataHome = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-xdg-data-'));
|
||||
const dataDir = path.posix.join(xdgDataHome, 'SubMiner');
|
||||
fs.mkdirSync(path.join(dataDir, 'themes'), { recursive: true });
|
||||
fs.mkdirSync(path.join(dataDir, 'thumbnailers'), { recursive: true });
|
||||
fs.mkdirSync(path.join(dataDir, 'plugin/subminer'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dataDir, 'themes/subminer.rasi'), 'same theme\n');
|
||||
fs.writeFileSync(
|
||||
path.join(dataDir, 'thumbnailers/subminer-ffmpegthumbnailer.thumbnailer'),
|
||||
'[Thumbnailer Entry]\n',
|
||||
);
|
||||
fs.writeFileSync(path.join(dataDir, 'plugin/subminer/main.lua'), 'same plugin\n');
|
||||
fs.writeFileSync(
|
||||
path.join(dataDir, 'plugin/subminer/version.lua'),
|
||||
@@ -371,6 +418,12 @@ test('updateSupportAssetsFromRelease skips identical theme and up-to-date plugin
|
||||
path: dataDir,
|
||||
message: 'Theme already up to date.',
|
||||
},
|
||||
{
|
||||
status: 'skipped',
|
||||
component: 'thumbnailer',
|
||||
path: dataDir,
|
||||
message: 'Rofi thumbnailer already up to date.',
|
||||
},
|
||||
{
|
||||
status: 'skipped',
|
||||
component: 'plugin',
|
||||
@@ -396,8 +449,13 @@ test('updateSupportAssetsFromRelease updates changed theme and outdated plugin w
|
||||
const xdgDataHome = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-xdg-data-'));
|
||||
const dataDir = path.posix.join(xdgDataHome, 'SubMiner');
|
||||
fs.mkdirSync(path.join(dataDir, 'themes'), { recursive: true });
|
||||
fs.mkdirSync(path.join(dataDir, 'thumbnailers'), { recursive: true });
|
||||
fs.mkdirSync(path.join(dataDir, 'plugin/subminer'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dataDir, 'themes/subminer.rasi'), 'old theme\n');
|
||||
fs.writeFileSync(
|
||||
path.join(dataDir, 'thumbnailers/subminer-ffmpegthumbnailer.thumbnailer'),
|
||||
'[Old Thumbnailer]\n',
|
||||
);
|
||||
fs.writeFileSync(path.join(dataDir, 'plugin/subminer/main.lua'), 'old plugin\n');
|
||||
fs.writeFileSync(
|
||||
path.join(dataDir, 'plugin/subminer/version.lua'),
|
||||
@@ -406,6 +464,7 @@ test('updateSupportAssetsFromRelease updates changed theme and outdated plugin w
|
||||
fs.writeFileSync(path.join(dataDir, 'plugin/subminer/stale.lua'), 'stale\n');
|
||||
const { archive, tempDir } = makeSupportAssetsArchive({
|
||||
themeContent: 'new theme\n',
|
||||
thumbnailerContent: '[Thumbnailer Entry]\n',
|
||||
pluginVersion: '0.12.0',
|
||||
pluginMainContent: 'new plugin main\n',
|
||||
extraPluginFiles: [{ relativePath: 'fresh.lua', content: 'fresh\n' }],
|
||||
@@ -424,6 +483,12 @@ test('updateSupportAssetsFromRelease updates changed theme and outdated plugin w
|
||||
path: dataDir,
|
||||
message: 'Updated theme.',
|
||||
},
|
||||
{
|
||||
status: 'updated',
|
||||
component: 'thumbnailer',
|
||||
path: dataDir,
|
||||
message: 'Updated rofi thumbnailer.',
|
||||
},
|
||||
{
|
||||
status: 'updated',
|
||||
component: 'plugin',
|
||||
@@ -435,6 +500,13 @@ test('updateSupportAssetsFromRelease updates changed theme and outdated plugin w
|
||||
fs.readFileSync(path.join(dataDir, 'themes/subminer.rasi'), 'utf8'),
|
||||
'new theme\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(
|
||||
path.join(dataDir, 'thumbnailers/subminer-ffmpegthumbnailer.thumbnailer'),
|
||||
'utf8',
|
||||
),
|
||||
'[Thumbnailer Entry]\n',
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(dataDir, 'plugin/subminer/main.lua'), 'utf8'),
|
||||
'new plugin main\n',
|
||||
@@ -479,6 +551,12 @@ test('updateSupportAssetsFromRelease returns protected commands for managed root
|
||||
path: dataDir,
|
||||
command: true,
|
||||
},
|
||||
{
|
||||
status: 'protected',
|
||||
component: 'thumbnailer',
|
||||
path: dataDir,
|
||||
command: true,
|
||||
},
|
||||
{
|
||||
status: 'protected',
|
||||
component: 'plugin',
|
||||
@@ -488,6 +566,10 @@ test('updateSupportAssetsFromRelease returns protected commands for managed root
|
||||
],
|
||||
);
|
||||
assert.match(results[0]?.command ?? '', /themes\/subminer\.rasi/);
|
||||
assert.match(
|
||||
results[0]?.command ?? '',
|
||||
/thumbnailers\/subminer-ffmpegthumbnailer\.thumbnailer/,
|
||||
);
|
||||
assert.match(results[0]?.command ?? '', /plugin\/subminer/);
|
||||
} finally {
|
||||
fs.chmodSync(dataDir, originalMode);
|
||||
@@ -522,3 +604,25 @@ test('updateSupportAssetsFromRelease returns missing-asset when release plugin v
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('updateSupportAssetsFromRelease rejects archives without the rofi thumbnailer', async () => {
|
||||
const xdgDataHome = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-xdg-data-'));
|
||||
const dataDir = path.posix.join(xdgDataHome, 'SubMiner');
|
||||
fs.mkdirSync(path.join(dataDir, 'themes'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dataDir, 'themes/subminer.rasi'), 'managed theme\n');
|
||||
const { archive, tempDir } = makeSupportAssetsArchive({ includeThumbnailer: false });
|
||||
|
||||
try {
|
||||
const results = await runLinuxSupportAssetUpdate({ archive, xdgDataHome });
|
||||
|
||||
assert.deepEqual(results, [
|
||||
{
|
||||
status: 'missing-asset',
|
||||
message: 'Support asset archive is missing the rofi thumbnailer.',
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
fs.rmSync(xdgDataHome, { recursive: true, force: true });
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,13 +9,17 @@ import { compareSemverLike, findReleaseAsset } from './release-assets';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const THEME_RELATIVE_PATH = path.join('themes', 'subminer.rasi');
|
||||
const THUMBNAILER_RELATIVE_PATH = path.join(
|
||||
'thumbnailers',
|
||||
'subminer-ffmpegthumbnailer.thumbnailer',
|
||||
);
|
||||
const PLUGIN_ENTRYPOINT_RELATIVE_PATH = path.join('plugin', 'subminer', 'main.lua');
|
||||
const PLUGIN_VERSION_RELATIVE_PATH = path.join('plugin', 'subminer', 'version.lua');
|
||||
const PLUGIN_DIR_RELATIVE_PATH = path.join('plugin', 'subminer');
|
||||
|
||||
export interface SupportAssetsUpdateResult {
|
||||
status: 'updated' | 'skipped' | 'protected' | 'hash-mismatch' | 'missing-asset';
|
||||
component?: 'theme' | 'plugin';
|
||||
component?: 'theme' | 'thumbnailer' | 'plugin';
|
||||
path?: string;
|
||||
command?: string;
|
||||
message?: string;
|
||||
@@ -69,11 +73,12 @@ async function readInstalledPluginVersion(pluginDir: string): Promise<string | n
|
||||
async function detectManagedSupportAssetDataDirs(dataDirs: string[]): Promise<string[]> {
|
||||
const managedDataDirs: string[] = [];
|
||||
for (const dataDir of dataDirs) {
|
||||
const [hasTheme, hasPlugin] = await Promise.all([
|
||||
const [hasTheme, hasThumbnailer, hasPlugin] = await Promise.all([
|
||||
pathExists(path.join(dataDir, THEME_RELATIVE_PATH)),
|
||||
pathExists(path.join(dataDir, THUMBNAILER_RELATIVE_PATH)),
|
||||
pathExists(path.join(dataDir, PLUGIN_ENTRYPOINT_RELATIVE_PATH)),
|
||||
]);
|
||||
if (hasTheme || hasPlugin) {
|
||||
if (hasTheme || hasThumbnailer || hasPlugin) {
|
||||
managedDataDirs.push(dataDir);
|
||||
}
|
||||
}
|
||||
@@ -163,8 +168,14 @@ export function buildProtectedSupportAssetsCommand(
|
||||
`curl -fSL ${shellQuote(assetUrl)} -o "$tmp/subminer-assets.tar.gz"`,
|
||||
`printf '%s %s\\n' ${quotedExpectedSha256} "$tmp/subminer-assets.tar.gz" | sha256sum -c -`,
|
||||
'tar -xzf "$tmp/subminer-assets.tar.gz" -C "$tmp"',
|
||||
'test -f "$tmp/assets/themes/subminer.rasi"',
|
||||
'test -f "$tmp/assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer"',
|
||||
'test -f "$tmp/plugin/subminer/main.lua"',
|
||||
'test -f "$tmp/plugin/subminer/version.lua"',
|
||||
`sudo mkdir -p ${quotedDir}/themes`,
|
||||
`sudo cp "$tmp/assets/themes/subminer.rasi" ${quotedDir}/themes/subminer.rasi`,
|
||||
`sudo mkdir -p ${quotedDir}/thumbnailers`,
|
||||
`sudo cp "$tmp/assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer" ${quotedDir}/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer`,
|
||||
`sudo mkdir -p ${quotedDir}/plugin`,
|
||||
`sudo rm -rf ${quotedStagedPluginDir} ${quotedBackupPluginDir}`,
|
||||
`sudo cp -R "$tmp/plugin/subminer" ${quotedStagedPluginDir}`,
|
||||
@@ -216,6 +227,12 @@ export async function updateSupportAssetsFromRelease(options: {
|
||||
dataDir,
|
||||
'Support asset path is not a directory.',
|
||||
),
|
||||
makeSupportAssetResult(
|
||||
'skipped',
|
||||
'thumbnailer',
|
||||
dataDir,
|
||||
'Support asset path is not a directory.',
|
||||
),
|
||||
makeSupportAssetResult(
|
||||
'skipped',
|
||||
'plugin',
|
||||
@@ -244,6 +261,13 @@ export async function updateSupportAssetsFromRelease(options: {
|
||||
'Theme install requires a manual command.',
|
||||
command,
|
||||
),
|
||||
makeSupportAssetResult(
|
||||
'protected',
|
||||
'thumbnailer',
|
||||
dataDir,
|
||||
'Rofi thumbnailer install requires a manual command.',
|
||||
command,
|
||||
),
|
||||
makeSupportAssetResult(
|
||||
'protected',
|
||||
'plugin',
|
||||
@@ -284,6 +308,17 @@ export async function updateSupportAssetsFromRelease(options: {
|
||||
}
|
||||
const themeBytes = await fs.promises.readFile(themeSourcePath);
|
||||
|
||||
const thumbnailerSourcePath = path.join(tempDir, 'assets', THUMBNAILER_RELATIVE_PATH);
|
||||
if (!(await pathExists(thumbnailerSourcePath))) {
|
||||
return [
|
||||
{
|
||||
status: 'missing-asset',
|
||||
message: 'Support asset archive is missing the rofi thumbnailer.',
|
||||
},
|
||||
];
|
||||
}
|
||||
const thumbnailerBytes = await fs.promises.readFile(thumbnailerSourcePath);
|
||||
|
||||
const sourcePluginDir = path.join(tempDir, PLUGIN_DIR_RELATIVE_PATH);
|
||||
const sourcePluginEntrypoint = path.join(tempDir, PLUGIN_ENTRYPOINT_RELATIVE_PATH);
|
||||
if (!(await pathExists(sourcePluginEntrypoint))) {
|
||||
@@ -328,6 +363,33 @@ export async function updateSupportAssetsFromRelease(options: {
|
||||
);
|
||||
}
|
||||
|
||||
const targetThumbnailerPath = path.join(dataDir, THUMBNAILER_RELATIVE_PATH);
|
||||
const existingThumbnailerBytes = await readFileIfExists(targetThumbnailerPath);
|
||||
if (
|
||||
existingThumbnailerBytes &&
|
||||
Buffer.compare(existingThumbnailerBytes, thumbnailerBytes) === 0
|
||||
) {
|
||||
results.push(
|
||||
makeSupportAssetResult(
|
||||
'skipped',
|
||||
'thumbnailer',
|
||||
dataDir,
|
||||
'Rofi thumbnailer already up to date.',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await fs.promises.mkdir(path.dirname(targetThumbnailerPath), { recursive: true });
|
||||
await fs.promises.writeFile(targetThumbnailerPath, thumbnailerBytes);
|
||||
results.push(
|
||||
makeSupportAssetResult(
|
||||
'updated',
|
||||
'thumbnailer',
|
||||
dataDir,
|
||||
existingThumbnailerBytes ? 'Updated rofi thumbnailer.' : 'Installed rofi thumbnailer.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const targetPluginDir = path.join(dataDir, PLUGIN_DIR_RELATIVE_PATH);
|
||||
const targetPluginEntrypoint = path.join(dataDir, PLUGIN_ENTRYPOINT_RELATIVE_PATH);
|
||||
const installedPluginVersion = await readInstalledPluginVersion(targetPluginDir);
|
||||
|
||||
Reference in New Issue
Block a user