1111import json
1212import logging
1313import os
14+ import re
15+ import sqlite3
1416import stat
1517import threading
16- from typing import Optional , List
1718
18- from .exception import (CorruptAccountInfo , MissingAccountData )
19- from .upload_url_pool import UrlPoolAccountInfo
19+ from typing import List , Optional
2020
21- import sqlite3
21+ from .exception import CorruptAccountInfo , MissingAccountData
22+ from .upload_url_pool import UrlPoolAccountInfo
2223
2324logger = logging .getLogger (__name__ )
2425
2526B2_ACCOUNT_INFO_ENV_VAR = 'B2_ACCOUNT_INFO'
26- B2_ACCOUNT_INFO_DEFAULT_FILE = '~/.b2_account_info'
27+ B2_ACCOUNT_INFO_DEFAULT_FILE = os .path .join ('~' , '.b2_account_info' )
28+ B2_ACCOUNT_INFO_PROFILE_FILE = os .path .join ('~' , '.b2db-{profile}.sqlite' )
29+ B2_ACCOUNT_INFO_PROFILE_NAME_REGEXP = re .compile (r'[a-zA-Z0-9_\-]{1,64}' )
2730XDG_CONFIG_HOME_ENV_VAR = 'XDG_CONFIG_HOME'
2831
2932DEFAULT_ABSOLUTE_MINIMUM_PART_SIZE = 5000000 # this value is used ONLY in migrating db, and in v1 wrapper, it is not
@@ -39,7 +42,7 @@ class SqliteAccountInfo(UrlPoolAccountInfo):
3942 completed.
4043 """
4144
42- def __init__ (self , file_name = None , last_upgrade_to_run = None ):
45+ def __init__ (self , file_name = None , last_upgrade_to_run = None , profile : Optional [ str ] = None ):
4346 """
4447 Initialize SqliteAccountInfo.
4548
@@ -49,6 +52,11 @@ def __init__(self, file_name=None, last_upgrade_to_run=None):
4952
5053 SqliteAccountInfo currently checks locations in the following order:
5154
55+ If ``profile`` arg is provided:
56+ * ``{XDG_CONFIG_HOME_ENV_VAR}/b2/db-<profile>.sqlite``, if ``{XDG_CONFIG_HOME_ENV_VAR}`` env var is set
57+ * ``{B2_ACCOUNT_INFO_PROFILE_FILE}``
58+
59+ Otherwise:
5260 * ``file_name``, if truthy
5361 * ``{B2_ACCOUNT_INFO_ENV_VAR}`` env var's value, if set
5462 * ``{B2_ACCOUNT_INFO_DEFAULT_FILE}``, if it exists
@@ -62,21 +70,7 @@ def __init__(self, file_name=None, last_upgrade_to_run=None):
6270 """
6371 self .thread_local = threading .local ()
6472
65- if file_name :
66- user_account_info_path = file_name
67- elif B2_ACCOUNT_INFO_ENV_VAR in os .environ :
68- user_account_info_path = os .environ [B2_ACCOUNT_INFO_ENV_VAR ]
69- elif os .path .exists (os .path .expanduser (B2_ACCOUNT_INFO_DEFAULT_FILE )):
70- user_account_info_path = B2_ACCOUNT_INFO_DEFAULT_FILE
71- elif XDG_CONFIG_HOME_ENV_VAR in os .environ :
72- config_home = os .environ [XDG_CONFIG_HOME_ENV_VAR ]
73- user_account_info_path = os .path .join (config_home , 'b2' , 'account_info' )
74- if not os .path .exists (os .path .join (config_home , 'b2' )):
75- os .makedirs (os .path .join (config_home , 'b2' ), mode = 0o755 )
76- else :
77- user_account_info_path = B2_ACCOUNT_INFO_DEFAULT_FILE
78-
79- self .filename = os .path .expanduser (user_account_info_path )
73+ self .filename = self ._get_user_account_info_path (file_name = file_name , profile = profile )
8074 logger .debug ('%s file path to use: %s' , self .__class__ .__name__ , self .filename )
8175
8276 self ._validate_database (last_upgrade_to_run )
@@ -90,10 +84,42 @@ def __init__(self, file_name=None, last_upgrade_to_run=None):
9084 ** dict (
9185 B2_ACCOUNT_INFO_ENV_VAR = B2_ACCOUNT_INFO_ENV_VAR ,
9286 B2_ACCOUNT_INFO_DEFAULT_FILE = B2_ACCOUNT_INFO_DEFAULT_FILE ,
87+ B2_ACCOUNT_INFO_PROFILE_FILE = B2_ACCOUNT_INFO_PROFILE_FILE ,
9388 XDG_CONFIG_HOME_ENV_VAR = XDG_CONFIG_HOME_ENV_VAR ,
9489 )
9590 )
9691
92+ @classmethod
93+ def _get_user_account_info_path (cls , file_name = None , profile = None ):
94+ if profile and not B2_ACCOUNT_INFO_PROFILE_NAME_REGEXP .match (profile ):
95+ raise ValueError ('Invalid profile name: {}' .format (profile ))
96+
97+ if file_name :
98+ if profile :
99+ raise ValueError ('Provide either file_name or profile, not both' )
100+ user_account_info_path = file_name
101+ elif B2_ACCOUNT_INFO_ENV_VAR in os .environ :
102+ if profile :
103+ raise ValueError (
104+ 'Provide either {} env var or profile, not both' .
105+ format (B2_ACCOUNT_INFO_ENV_VAR )
106+ )
107+ user_account_info_path = os .environ [B2_ACCOUNT_INFO_ENV_VAR ]
108+ elif os .path .exists (os .path .expanduser (B2_ACCOUNT_INFO_DEFAULT_FILE )) and not profile :
109+ user_account_info_path = B2_ACCOUNT_INFO_DEFAULT_FILE
110+ elif XDG_CONFIG_HOME_ENV_VAR in os .environ :
111+ config_home = os .environ [XDG_CONFIG_HOME_ENV_VAR ]
112+ file_name = 'db-{}.sqlite' .format (profile ) if profile else 'account_info'
113+ user_account_info_path = os .path .join (config_home , 'b2' , file_name )
114+ if not os .path .exists (os .path .join (config_home , 'b2' )):
115+ os .makedirs (os .path .join (config_home , 'b2' ), mode = 0o755 )
116+ elif profile :
117+ user_account_info_path = B2_ACCOUNT_INFO_PROFILE_FILE .format (profile = profile )
118+ else :
119+ user_account_info_path = B2_ACCOUNT_INFO_DEFAULT_FILE
120+
121+ return os .path .expanduser (user_account_info_path )
122+
97123 def _validate_database (self , last_upgrade_to_run = None ):
98124 """
99125 Make sure that the database is openable. Removes the file if it's not.
0 commit comments