mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-09-24 15:10:26 +01:00
Fix: Config initialization and category fallback (#442)
- Added more robust config directory initialisation and file creation - Fixed category fallback not triggering correctly for one content type if another is cached
This commit is contained in:
@@ -284,7 +284,78 @@ def save_config_file(tab_name: str, values: Dict[str, Any]) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def initialize_default_configs() -> bool:
|
||||
"""Initialize config files with default values on first startup.
|
||||
|
||||
Creates config files for all settings tabs that don't have one yet,
|
||||
populating them with field default values. This ensures config files
|
||||
exist from first startup rather than only being created on explicit save.
|
||||
|
||||
Returns:
|
||||
True if initialization succeeded or was skipped (already initialized),
|
||||
False if there was an error accessing the config directory.
|
||||
"""
|
||||
try:
|
||||
config_dir = _get_config_dir()
|
||||
|
||||
# Check if config directory exists and is writable
|
||||
if not config_dir.exists():
|
||||
logger.warning(f"Config directory does not exist: {config_dir}")
|
||||
return False
|
||||
|
||||
# Test writability
|
||||
test_file = config_dir / ".write_test"
|
||||
try:
|
||||
test_file.touch()
|
||||
test_file.unlink()
|
||||
except (OSError, PermissionError) as e:
|
||||
logger.warning(f"Config directory is not writable: {config_dir} - {e}")
|
||||
return False
|
||||
|
||||
initialized_tabs = []
|
||||
|
||||
for tab in get_all_settings_tabs():
|
||||
config_path = _get_config_file_path(tab.name)
|
||||
|
||||
# Skip if config file already exists
|
||||
if config_path.exists():
|
||||
continue
|
||||
|
||||
# Collect default values for all fields
|
||||
defaults = {}
|
||||
for field in tab.fields:
|
||||
# Skip non-value fields
|
||||
if isinstance(field, (ActionButton, HeadingField)):
|
||||
continue
|
||||
|
||||
# Only include fields that have a non-None default
|
||||
if field.default is not None:
|
||||
defaults[field.key] = field.default
|
||||
|
||||
# Create config file with defaults if we have any
|
||||
if defaults:
|
||||
_ensure_config_dir(tab.name)
|
||||
try:
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(defaults, f, indent=2)
|
||||
initialized_tabs.append(tab.name)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize config for {tab.name}: {e}")
|
||||
|
||||
if initialized_tabs:
|
||||
logger.info(f"Initialized default configs for: {initialized_tabs}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during config initialization: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def sync_env_to_config() -> None:
|
||||
# Initialize default configs first (for fresh installs)
|
||||
initialize_default_configs()
|
||||
|
||||
for tab in get_all_settings_tabs():
|
||||
values_to_sync = {}
|
||||
|
||||
|
||||
@@ -12,20 +12,20 @@ import { LanguageMultiSelect } from './LanguageMultiSelect';
|
||||
import { LANGUAGE_OPTION_ALL, LANGUAGE_OPTION_DEFAULT, getLanguageFilterValues, releaseLanguageMatchesFilter } from '../utils/languageFilters';
|
||||
|
||||
// Module-level cache for release search results
|
||||
// Key format: `${provider}:${provider_id}:${source}`
|
||||
// Key format: `${provider}:${provider_id}:${source}:${contentType}`
|
||||
// This persists across modal open/close cycles
|
||||
const releaseCache = new Map<string, ReleasesResponse>();
|
||||
|
||||
function getCacheKey(provider: string, providerId: string, source: string): string {
|
||||
return `${provider}:${providerId}:${source}`;
|
||||
function getCacheKey(provider: string, providerId: string, source: string, contentType: string): string {
|
||||
return `${provider}:${providerId}:${source}:${contentType}`;
|
||||
}
|
||||
|
||||
// Default cache TTL (5 minutes) - sources can override via column_config.cache_ttl_seconds
|
||||
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
const cacheTimestamps = new Map<string, number>();
|
||||
|
||||
function getCachedReleases(provider: string, providerId: string, source: string): ReleasesResponse | null {
|
||||
const key = getCacheKey(provider, providerId, source);
|
||||
function getCachedReleases(provider: string, providerId: string, source: string, contentType: string): ReleasesResponse | null {
|
||||
const key = getCacheKey(provider, providerId, source, contentType);
|
||||
const timestamp = cacheTimestamps.get(key);
|
||||
const cached = releaseCache.get(key);
|
||||
|
||||
@@ -49,8 +49,8 @@ function getCachedReleases(provider: string, providerId: string, source: string)
|
||||
return null;
|
||||
}
|
||||
|
||||
function setCachedReleases(provider: string, providerId: string, source: string, data: ReleasesResponse): void {
|
||||
const key = getCacheKey(provider, providerId, source);
|
||||
function setCachedReleases(provider: string, providerId: string, source: string, contentType: string, data: ReleasesResponse): void {
|
||||
const key = getCacheKey(provider, providerId, source, contentType);
|
||||
releaseCache.set(key, data);
|
||||
cacheTimestamps.set(key, Date.now());
|
||||
}
|
||||
@@ -833,7 +833,7 @@ export const ReleaseModal = ({
|
||||
if (releasesBySource[activeTab] !== undefined || loadingBySource[activeTab] || errorBySource[activeTab]) return;
|
||||
|
||||
// Check module-level cache first
|
||||
const cached = getCachedReleases(provider, bookId, activeTab);
|
||||
const cached = getCachedReleases(provider, bookId, activeTab, contentType);
|
||||
if (cached) {
|
||||
setReleasesBySource((prev) => ({ ...prev, [activeTab]: cached }));
|
||||
return;
|
||||
@@ -845,7 +845,7 @@ export const ReleaseModal = ({
|
||||
|
||||
try {
|
||||
const response = await getReleases(provider, bookId, activeTab, book.title, book.author, undefined, undefined, contentType);
|
||||
setCachedReleases(provider, bookId, activeTab, response);
|
||||
setCachedReleases(provider, bookId, activeTab, contentType, response);
|
||||
setReleasesBySource((prev) => ({ ...prev, [activeTab]: response }));
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to fetch releases';
|
||||
@@ -1550,7 +1550,7 @@ export const ReleaseModal = ({
|
||||
const bookId = book.provider_id;
|
||||
|
||||
// Clear cache and state
|
||||
const key = getCacheKey(provider, bookId, activeTab);
|
||||
const key = getCacheKey(provider, bookId, activeTab, contentType);
|
||||
releaseCache.delete(key);
|
||||
cacheTimestamps.delete(key);
|
||||
setExpandedBySource((prev) => {
|
||||
@@ -1577,7 +1577,7 @@ export const ReleaseModal = ({
|
||||
const response = await getReleases(
|
||||
provider, bookId, activeTab, book.title, book.author, false, languagesParam, contentType
|
||||
);
|
||||
setCachedReleases(provider, bookId, activeTab, response);
|
||||
setCachedReleases(provider, bookId, activeTab, contentType, response);
|
||||
setReleasesBySource((prev) => ({ ...prev, [activeTab]: response }));
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to fetch releases';
|
||||
|
||||
Reference in New Issue
Block a user