mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
Disable character-dictionary features in external profile mode
- Gate character-dictionary runtime, auto-sync, annotations, and CLI generation when `yomitan.externalProfilePath` is set - Return explicit disabled reason for blocked character-dictionary generation in read-only external-profile mode - Fix default config bootstrap to seed `config.jsonc` when config dir exists but config file is missing - Update tests, changelog fragment, and docs to reflect the new behavior
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
type: added
|
type: added
|
||||||
area: yomitan
|
area: config
|
||||||
|
|
||||||
- Added `yomitan.externalProfilePath` to reuse another Electron app's Yomitan profile in read-only mode.
|
- Added `yomitan.externalProfilePath` to reuse another Electron app's Yomitan profile in read-only mode.
|
||||||
- SubMiner now reuses external Yomitan dictionaries/settings without writing back to that profile.
|
- SubMiner now reuses external Yomitan dictionaries/settings without writing back to that profile.
|
||||||
|
- Fixed default config bootstrap so `config.jsonc` is seeded even when the config directory already exists.
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ The first sync for a media title takes a few seconds while character data and po
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
::: warning
|
::: warning
|
||||||
If `yomitan.externalProfilePath` is set, SubMiner switches to read-only external-profile mode. In that mode SubMiner can reuse another app's installed Yomitan dictionaries/settings, but character-dictionary auto-sync does not import or update the merged dictionary.
|
If `yomitan.externalProfilePath` is set, SubMiner switches to read-only external-profile mode. In that mode SubMiner can reuse another app's installed Yomitan dictionaries/settings, but SubMiner's own character-dictionary features are fully disabled.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Name Generation
|
## Name Generation
|
||||||
|
|||||||
@@ -959,7 +959,7 @@ External-profile mode behavior:
|
|||||||
- SubMiner reads the external profile's currently active Yomitan profile selection and installed dictionaries.
|
- SubMiner reads the external profile's currently active Yomitan profile selection and installed dictionaries.
|
||||||
- SubMiner does not open its own Yomitan settings window in this mode.
|
- SubMiner does not open its own Yomitan settings window in this mode.
|
||||||
- SubMiner does not import, delete, or update dictionaries/settings in the external profile.
|
- SubMiner does not import, delete, or update dictionaries/settings in the external profile.
|
||||||
- SubMiner character-dictionary auto-sync is effectively disabled in this mode because it requires Yomitan writes.
|
- SubMiner character-dictionary features are fully disabled in this mode, including auto-sync, manual generation, and subtitle-side character-dictionary annotations.
|
||||||
|
|
||||||
### Jellyfin
|
### Jellyfin
|
||||||
|
|
||||||
|
|||||||
32
src/main.ts
32
src/main.ts
@@ -374,6 +374,10 @@ import { createOverlayVisibilityRuntimeService } from './main/overlay-visibility
|
|||||||
import { createCharacterDictionaryRuntimeService } from './main/character-dictionary-runtime';
|
import { createCharacterDictionaryRuntimeService } from './main/character-dictionary-runtime';
|
||||||
import { createCharacterDictionaryAutoSyncRuntimeService } from './main/runtime/character-dictionary-auto-sync';
|
import { createCharacterDictionaryAutoSyncRuntimeService } from './main/runtime/character-dictionary-auto-sync';
|
||||||
import { notifyCharacterDictionaryAutoSyncStatus } from './main/runtime/character-dictionary-auto-sync-notifications';
|
import { notifyCharacterDictionaryAutoSyncStatus } from './main/runtime/character-dictionary-auto-sync-notifications';
|
||||||
|
import {
|
||||||
|
getCharacterDictionaryDisabledReason,
|
||||||
|
isCharacterDictionaryRuntimeEnabled,
|
||||||
|
} from './main/runtime/character-dictionary-availability';
|
||||||
import { createCurrentMediaTokenizationGate } from './main/runtime/current-media-tokenization-gate';
|
import { createCurrentMediaTokenizationGate } from './main/runtime/current-media-tokenization-gate';
|
||||||
import { createStartupOsdSequencer } from './main/runtime/startup-osd-sequencer';
|
import { createStartupOsdSequencer } from './main/runtime/startup-osd-sequencer';
|
||||||
import { formatSkippedYomitanWriteAction } from './main/runtime/yomitan-read-only-log';
|
import { formatSkippedYomitanWriteAction } from './main/runtime/yomitan-read-only-log';
|
||||||
@@ -1328,7 +1332,9 @@ const characterDictionaryAutoSyncRuntime = createCharacterDictionaryAutoSyncRunt
|
|||||||
getConfig: () => {
|
getConfig: () => {
|
||||||
const config = getResolvedConfig().anilist.characterDictionary;
|
const config = getResolvedConfig().anilist.characterDictionary;
|
||||||
return {
|
return {
|
||||||
enabled: config.enabled,
|
enabled:
|
||||||
|
config.enabled &&
|
||||||
|
isCharacterDictionaryRuntimeEnabled(getConfiguredExternalYomitanProfilePath()),
|
||||||
maxLoaded: config.maxLoaded,
|
maxLoaded: config.maxLoaded,
|
||||||
profileScope: config.profileScope,
|
profileScope: config.profileScope,
|
||||||
};
|
};
|
||||||
@@ -2756,6 +2762,9 @@ const {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
scheduleCharacterDictionarySync: () => {
|
scheduleCharacterDictionarySync: () => {
|
||||||
|
if (!isCharacterDictionaryEnabledForCurrentProcess()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
characterDictionaryAutoSyncRuntime.scheduleSync();
|
characterDictionaryAutoSyncRuntime.scheduleSync();
|
||||||
},
|
},
|
||||||
updateCurrentMediaTitle: (title) => {
|
updateCurrentMediaTitle: (title) => {
|
||||||
@@ -2833,7 +2842,9 @@ const {
|
|||||||
'subtitle.annotation.jlpt',
|
'subtitle.annotation.jlpt',
|
||||||
getResolvedConfig().subtitleStyle.enableJlpt,
|
getResolvedConfig().subtitleStyle.enableJlpt,
|
||||||
),
|
),
|
||||||
getCharacterDictionaryEnabled: () => getResolvedConfig().anilist.characterDictionary.enabled,
|
getCharacterDictionaryEnabled: () =>
|
||||||
|
getResolvedConfig().anilist.characterDictionary.enabled &&
|
||||||
|
isCharacterDictionaryEnabledForCurrentProcess(),
|
||||||
getNameMatchEnabled: () => getResolvedConfig().subtitleStyle.nameMatchEnabled,
|
getNameMatchEnabled: () => getResolvedConfig().subtitleStyle.nameMatchEnabled,
|
||||||
getFrequencyDictionaryEnabled: () =>
|
getFrequencyDictionaryEnabled: () =>
|
||||||
getRuntimeBooleanOption(
|
getRuntimeBooleanOption(
|
||||||
@@ -3035,6 +3046,14 @@ function isYomitanExternalReadOnlyMode(): boolean {
|
|||||||
return getConfiguredExternalYomitanProfilePath().length > 0;
|
return getConfiguredExternalYomitanProfilePath().length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCharacterDictionaryEnabledForCurrentProcess(): boolean {
|
||||||
|
return isCharacterDictionaryRuntimeEnabled(getConfiguredExternalYomitanProfilePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCharacterDictionaryDisabledReasonForCurrentProcess(): string | null {
|
||||||
|
return getCharacterDictionaryDisabledReason(getConfiguredExternalYomitanProfilePath());
|
||||||
|
}
|
||||||
|
|
||||||
function logSkippedYomitanWrite(action: string): void {
|
function logSkippedYomitanWrite(action: string): void {
|
||||||
logger.info(
|
logger.info(
|
||||||
`[yomitan] skipping ${action}: yomitan.externalProfilePath is configured; external profile mode is read-only`,
|
`[yomitan] skipping ${action}: yomitan.externalProfilePath is configured; external profile mode is read-only`,
|
||||||
@@ -3537,8 +3556,13 @@ const createCliCommandContextHandler = createCliCommandContextFactory({
|
|||||||
openJellyfinSetupWindow: () => openJellyfinSetupWindow(),
|
openJellyfinSetupWindow: () => openJellyfinSetupWindow(),
|
||||||
getAnilistQueueStatus: () => anilistStateRuntime.getQueueStatusSnapshot(),
|
getAnilistQueueStatus: () => anilistStateRuntime.getQueueStatusSnapshot(),
|
||||||
processNextAnilistRetryUpdate: () => processNextAnilistRetryUpdate(),
|
processNextAnilistRetryUpdate: () => processNextAnilistRetryUpdate(),
|
||||||
generateCharacterDictionary: (targetPath?: string) =>
|
generateCharacterDictionary: async (targetPath?: string) => {
|
||||||
characterDictionaryRuntime.generateForCurrentMedia(targetPath),
|
const disabledReason = getCharacterDictionaryDisabledReasonForCurrentProcess();
|
||||||
|
if (disabledReason) {
|
||||||
|
throw new Error(disabledReason);
|
||||||
|
}
|
||||||
|
return await characterDictionaryRuntime.generateForCurrentMedia(targetPath);
|
||||||
|
},
|
||||||
runJellyfinCommand: (argsFromCommand: CliArgs) => runJellyfinCommand(argsFromCommand),
|
runJellyfinCommand: (argsFromCommand: CliArgs) => runJellyfinCommand(argsFromCommand),
|
||||||
openYomitanSettings: () => openYomitanSettings(),
|
openYomitanSettings: () => openYomitanSettings(),
|
||||||
cycleSecondarySubMode: () => handleCycleSecondarySubMode(),
|
cycleSecondarySubMode: () => handleCycleSecondarySubMode(),
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ test('ensureDefaultConfigBootstrap creates config dir and default jsonc only whe
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ensureDefaultConfigBootstrap does not seed default config into an existing config directory', () => {
|
test('ensureDefaultConfigBootstrap seeds default config into an existing config directory when missing', () => {
|
||||||
withTempDir((root) => {
|
withTempDir((root) => {
|
||||||
const configDir = path.join(root, 'SubMiner');
|
const configDir = path.join(root, 'SubMiner');
|
||||||
fs.mkdirSync(configDir, { recursive: true });
|
fs.mkdirSync(configDir, { recursive: true });
|
||||||
@@ -74,10 +74,13 @@ test('ensureDefaultConfigBootstrap does not seed default config into an existing
|
|||||||
ensureDefaultConfigBootstrap({
|
ensureDefaultConfigBootstrap({
|
||||||
configDir,
|
configDir,
|
||||||
configFilePaths: getDefaultConfigFilePaths(configDir),
|
configFilePaths: getDefaultConfigFilePaths(configDir),
|
||||||
generateTemplate: () => 'should-not-write',
|
generateTemplate: () => '{\n "logging": {}\n}\n',
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(fs.existsSync(path.join(configDir, 'config.jsonc')), false);
|
assert.equal(
|
||||||
|
fs.readFileSync(path.join(configDir, 'config.jsonc'), 'utf8'),
|
||||||
|
'{\n "logging": {}\n}\n',
|
||||||
|
);
|
||||||
assert.equal(fs.readFileSync(path.join(configDir, 'existing-user-file.txt'), 'utf8'), 'keep\n');
|
assert.equal(fs.readFileSync(path.join(configDir, 'existing-user-file.txt'), 'utf8'), 'keep\n');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -208,13 +208,8 @@ export function ensureDefaultConfigBootstrap(options: {
|
|||||||
const existsSync = options.existsSync ?? fs.existsSync;
|
const existsSync = options.existsSync ?? fs.existsSync;
|
||||||
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
||||||
const writeFileSync = options.writeFileSync ?? fs.writeFileSync;
|
const writeFileSync = options.writeFileSync ?? fs.writeFileSync;
|
||||||
const configDirExists = existsSync(options.configDir);
|
|
||||||
|
|
||||||
if (
|
if (existsSync(options.configFilePaths.jsoncPath) || existsSync(options.configFilePaths.jsonPath)) {
|
||||||
existsSync(options.configFilePaths.jsoncPath) ||
|
|
||||||
existsSync(options.configFilePaths.jsonPath) ||
|
|
||||||
configDirExists
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user