6
6
__author__ = 'Simon Robinson'
7
7
__copyright__ = 'Copyright (c) 2023 Simon Robinson'
8
8
__license__ = 'Apache 2.0'
9
- __version__ = '2023-08-16 ' # ISO 8601 (YYYY-MM-DD)
9
+ __version__ = '2023-08-26 ' # ISO 8601 (YYYY-MM-DD)
10
10
11
11
import abc
12
12
import argparse
@@ -462,7 +462,7 @@ class AppConfig:
462
462
"""Helper wrapper around ConfigParser to cache servers/accounts, and avoid writing to the file until necessary"""
463
463
464
464
_PARSER = None
465
- _LOADED = False
465
+ _PARSER_LOCK = threading . Lock ()
466
466
467
467
# note: removing the unencrypted version of `client_secret_encrypted` is not automatic with --cache-store (see docs)
468
468
_CACHED_OPTION_KEYS = ['token_salt' , 'access_token' , 'access_token_expiry' , 'refresh_token' , 'last_activity' ,
@@ -473,27 +473,26 @@ class AppConfig:
473
473
474
474
@staticmethod
475
475
def _load ():
476
- AppConfig .unload ()
477
- AppConfig ._PARSER = ConcurrentConfigParser ()
478
- AppConfig ._PARSER .read (CONFIG_FILE_PATH )
476
+ config_parser = ConcurrentConfigParser ()
477
+ config_parser .read (CONFIG_FILE_PATH )
479
478
480
479
# cached account credentials can be stored in the configuration file (default) or, via `--cache-store`, a
481
480
# separate local file or external service (such as a secrets manager) - we combine these sources at load time
482
481
if CACHE_STORE != CONFIG_FILE_PATH :
483
482
# it would be cleaner to avoid specific options here, but best to load unexpected sections only when enabled
484
- allow_catch_all_accounts = AppConfig . _PARSER .getboolean (APP_SHORT_NAME , 'allow_catch_all_accounts' ,
485
- fallback = False )
483
+ allow_catch_all_accounts = config_parser .getboolean (APP_SHORT_NAME , 'allow_catch_all_accounts' ,
484
+ fallback = False )
486
485
487
486
cache_file_parser = AppConfig ._load_cache (CACHE_STORE )
488
487
cache_file_accounts = [s for s in cache_file_parser .sections () if '@' in s ]
489
488
for account in cache_file_accounts :
490
- if allow_catch_all_accounts and account not in AppConfig . _PARSER .sections (): # missing sub-accounts
491
- AppConfig . _PARSER .add_section (account )
489
+ if allow_catch_all_accounts and account not in config_parser .sections (): # missing sub-accounts
490
+ config_parser .add_section (account )
492
491
for option in cache_file_parser .options (account ):
493
492
if option in AppConfig ._CACHED_OPTION_KEYS :
494
- AppConfig . _PARSER .set (account , option , cache_file_parser .get (account , option ))
493
+ config_parser .set (account , option , cache_file_parser .get (account , option ))
495
494
496
- AppConfig . _LOADED = True
495
+ return config_parser
497
496
498
497
@staticmethod
499
498
def _load_cache (cache_store_identifier ):
@@ -507,38 +506,34 @@ def _load_cache(cache_store_identifier):
507
506
508
507
@staticmethod
509
508
def get ():
510
- if not AppConfig ._LOADED :
511
- AppConfig ._load ()
512
- return AppConfig ._PARSER
509
+ with AppConfig ._PARSER_LOCK :
510
+ if AppConfig ._PARSER is None :
511
+ AppConfig ._PARSER = AppConfig ._load ()
512
+ return AppConfig ._PARSER
513
513
514
514
@staticmethod
515
515
def unload ():
516
- AppConfig ._PARSER = None
517
- AppConfig ._LOADED = False
518
-
519
- @staticmethod
520
- def reload ():
521
- AppConfig .unload ()
522
- return AppConfig .get ()
516
+ with AppConfig ._PARSER_LOCK :
517
+ AppConfig ._PARSER = None
523
518
524
519
@staticmethod
525
520
def get_global (name , fallback ):
526
- AppConfig .get () # make sure config is loaded
527
- return AppConfig ._PARSER .getboolean (APP_SHORT_NAME , name , fallback )
521
+ return AppConfig .get ().getboolean (APP_SHORT_NAME , name , fallback )
528
522
529
523
@staticmethod
530
524
def servers ():
531
- AppConfig .get () # make sure config is loaded
532
- return [s for s in AppConfig ._PARSER .sections () if CONFIG_SERVER_MATCHER .match (s )]
525
+ return [s for s in AppConfig .get ().sections () if CONFIG_SERVER_MATCHER .match (s )]
533
526
534
527
@staticmethod
535
528
def accounts ():
536
- AppConfig .get () # make sure config is loaded
537
- return [s for s in AppConfig ._PARSER .sections () if '@' in s ]
529
+ return [s for s in AppConfig .get ().sections () if '@' in s ]
538
530
539
531
@staticmethod
540
532
def save ():
541
- if AppConfig ._LOADED :
533
+ with AppConfig ._PARSER_LOCK :
534
+ if AppConfig ._PARSER is None : # intentionally using _PARSER not get() so we don't (re)load if unloaded
535
+ return
536
+
542
537
if CACHE_STORE != CONFIG_FILE_PATH :
543
538
# in `--cache-store` mode we ignore everything except _CACHED_OPTION_KEYS (OAuth 2.0 tokens, etc)
544
539
output_config_parser = configparser .ConfigParser ()
@@ -554,8 +549,7 @@ def save():
554
549
if section not in config_accounts or len (output_config_parser .options (section )) <= 0 :
555
550
output_config_parser .remove_section (section )
556
551
557
- with AppConfig ._PARSER .lock :
558
- AppConfig ._save_cache (CACHE_STORE , output_config_parser )
552
+ AppConfig ._save_cache (CACHE_STORE , output_config_parser )
559
553
560
554
else :
561
555
# by default we cache to the local configuration file, and rewrite all values each time
@@ -646,7 +640,7 @@ def get_account_with_catch_all_fallback(option):
646
640
647
641
# try reloading remotely cached tokens if possible
648
642
if not access_token and CACHE_STORE != CONFIG_FILE_PATH and recurse_retries :
649
- AppConfig .reload ()
643
+ AppConfig .unload ()
650
644
return OAuth2Helper .get_oauth2_credentials (username , password , recurse_retries = False )
651
645
652
646
# we hash locally-stored tokens with the given password
@@ -2782,7 +2776,9 @@ def load_and_start_servers(self, icon=None, reload=True):
2782
2776
# we allow reloading, so must first stop any existing servers
2783
2777
self .stop_servers ()
2784
2778
Log .info ('Initialising' , APP_NAME , '(version %s)' % __version__ , 'from config file' , CONFIG_FILE_PATH )
2785
- config = AppConfig .reload () if reload else AppConfig .get ()
2779
+ if reload :
2780
+ AppConfig .unload ()
2781
+ config = AppConfig .get ()
2786
2782
2787
2783
# load server types and configurations
2788
2784
server_load_error = False
0 commit comments