mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 03:16:46 -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
|
||||
area: yomitan
|
||||
area: config
|
||||
|
||||
- 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.
|
||||
- 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
|
||||
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
|
||||
|
||||
@@ -959,7 +959,7 @@ External-profile mode behavior:
|
||||
- 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 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
|
||||
|
||||
|
||||
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 { createCharacterDictionaryAutoSyncRuntimeService } from './main/runtime/character-dictionary-auto-sync';
|
||||
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 { createStartupOsdSequencer } from './main/runtime/startup-osd-sequencer';
|
||||
import { formatSkippedYomitanWriteAction } from './main/runtime/yomitan-read-only-log';
|
||||
@@ -1328,7 +1332,9 @@ const characterDictionaryAutoSyncRuntime = createCharacterDictionaryAutoSyncRunt
|
||||
getConfig: () => {
|
||||
const config = getResolvedConfig().anilist.characterDictionary;
|
||||
return {
|
||||
enabled: config.enabled,
|
||||
enabled:
|
||||
config.enabled &&
|
||||
isCharacterDictionaryRuntimeEnabled(getConfiguredExternalYomitanProfilePath()),
|
||||
maxLoaded: config.maxLoaded,
|
||||
profileScope: config.profileScope,
|
||||
};
|
||||
@@ -2756,6 +2762,9 @@ const {
|
||||
);
|
||||
},
|
||||
scheduleCharacterDictionarySync: () => {
|
||||
if (!isCharacterDictionaryEnabledForCurrentProcess()) {
|
||||
return;
|
||||
}
|
||||
characterDictionaryAutoSyncRuntime.scheduleSync();
|
||||
},
|
||||
updateCurrentMediaTitle: (title) => {
|
||||
@@ -2833,7 +2842,9 @@ const {
|
||||
'subtitle.annotation.jlpt',
|
||||
getResolvedConfig().subtitleStyle.enableJlpt,
|
||||
),
|
||||
getCharacterDictionaryEnabled: () => getResolvedConfig().anilist.characterDictionary.enabled,
|
||||
getCharacterDictionaryEnabled: () =>
|
||||
getResolvedConfig().anilist.characterDictionary.enabled &&
|
||||
isCharacterDictionaryEnabledForCurrentProcess(),
|
||||
getNameMatchEnabled: () => getResolvedConfig().subtitleStyle.nameMatchEnabled,
|
||||
getFrequencyDictionaryEnabled: () =>
|
||||
getRuntimeBooleanOption(
|
||||
@@ -3035,6 +3046,14 @@ function isYomitanExternalReadOnlyMode(): boolean {
|
||||
return getConfiguredExternalYomitanProfilePath().length > 0;
|
||||
}
|
||||
|
||||
function isCharacterDictionaryEnabledForCurrentProcess(): boolean {
|
||||
return isCharacterDictionaryRuntimeEnabled(getConfiguredExternalYomitanProfilePath());
|
||||
}
|
||||
|
||||
function getCharacterDictionaryDisabledReasonForCurrentProcess(): string | null {
|
||||
return getCharacterDictionaryDisabledReason(getConfiguredExternalYomitanProfilePath());
|
||||
}
|
||||
|
||||
function logSkippedYomitanWrite(action: string): void {
|
||||
logger.info(
|
||||
`[yomitan] skipping ${action}: yomitan.externalProfilePath is configured; external profile mode is read-only`,
|
||||
@@ -3537,8 +3556,13 @@ const createCliCommandContextHandler = createCliCommandContextFactory({
|
||||
openJellyfinSetupWindow: () => openJellyfinSetupWindow(),
|
||||
getAnilistQueueStatus: () => anilistStateRuntime.getQueueStatusSnapshot(),
|
||||
processNextAnilistRetryUpdate: () => processNextAnilistRetryUpdate(),
|
||||
generateCharacterDictionary: (targetPath?: string) =>
|
||||
characterDictionaryRuntime.generateForCurrentMedia(targetPath),
|
||||
generateCharacterDictionary: async (targetPath?: string) => {
|
||||
const disabledReason = getCharacterDictionaryDisabledReasonForCurrentProcess();
|
||||
if (disabledReason) {
|
||||
throw new Error(disabledReason);
|
||||
}
|
||||
return await characterDictionaryRuntime.generateForCurrentMedia(targetPath);
|
||||
},
|
||||
runJellyfinCommand: (argsFromCommand: CliArgs) => runJellyfinCommand(argsFromCommand),
|
||||
openYomitanSettings: () => openYomitanSettings(),
|
||||
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) => {
|
||||
const configDir = path.join(root, 'SubMiner');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
@@ -74,10 +74,13 @@ test('ensureDefaultConfigBootstrap does not seed default config into an existing
|
||||
ensureDefaultConfigBootstrap({
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -208,13 +208,8 @@ export function ensureDefaultConfigBootstrap(options: {
|
||||
const existsSync = options.existsSync ?? fs.existsSync;
|
||||
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
||||
const writeFileSync = options.writeFileSync ?? fs.writeFileSync;
|
||||
const configDirExists = existsSync(options.configDir);
|
||||
|
||||
if (
|
||||
existsSync(options.configFilePaths.jsoncPath) ||
|
||||
existsSync(options.configFilePaths.jsonPath) ||
|
||||
configDirExists
|
||||
) {
|
||||
if (existsSync(options.configFilePaths.jsoncPath) || existsSync(options.configFilePaths.jsonPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user