Merge branch 'blender-v4.0-release'
[blender-addons.git] / blender_id / profiles.py
blob8b833946bf35316daeabb5e7ad91cf4586d9b628
1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import os
6 import bpy
8 from . import communication
10 # Set/created upon register.
11 profiles_path = ''
12 profiles_file = ''
15 class _BIPMeta(type):
16 """Metaclass for BlenderIdProfile."""
18 def __str__(self):
19 # noinspection PyUnresolvedReferences
20 return '%s(user_id=%r)' % (self.__qualname__, self.user_id)
23 class BlenderIdProfile(metaclass=_BIPMeta):
24 """Current Blender ID profile.
26 This is always stored at class level, as there is only one current
27 profile anyway.
28 """
30 user_id = ''
31 username = ''
32 token = ''
33 expires = ''
34 subclients = {}
36 @classmethod
37 def reset(cls):
38 cls.user_id = ''
39 cls.username = ''
40 cls.token = ''
41 cls.expires = ''
42 cls.subclients = {}
44 @classmethod
45 def read_json(cls):
46 """Updates the active profile information from the JSON file."""
48 cls.reset()
50 active_profile = get_active_profile()
51 if not active_profile:
52 return
54 for key, value in active_profile.items():
55 if hasattr(cls, key):
56 setattr(cls, key, value)
57 else:
58 print('Skipping key %r from profile JSON' % key)
60 @classmethod
61 def save_json(cls, make_active_profile=False):
62 """Updates the JSON file with the active profile information."""
64 jsonfile = get_profiles_data()
65 jsonfile['profiles'][cls.user_id] = {
66 'username': cls.username,
67 'token': cls.token,
68 'expires': cls.expires,
69 'subclients': cls.subclients,
72 if make_active_profile:
73 jsonfile['active_profile'] = cls.user_id
75 save_profiles_data(jsonfile)
78 def register():
79 global profiles_path, profiles_file
81 profiles_path = bpy.utils.user_resource('CONFIG', path='blender_id', create=True)
82 profiles_file = os.path.join(profiles_path, 'profiles.json')
85 def _create_default_file():
86 """Creates the default profile file, returning its contents."""
87 import json
89 profiles_default_data = {
90 'active_profile': None,
91 'profiles': {}
94 os.makedirs(profiles_path, exist_ok=True)
96 # Populate the file, ensuring that its permissions are restrictive enough.
97 old_umask = os.umask(0o077)
98 try:
99 with open(profiles_file, 'w', encoding='utf8') as outfile:
100 json.dump(profiles_default_data, outfile)
101 finally:
102 os.umask(old_umask)
104 return profiles_default_data
107 def get_profiles_data():
108 """Returns the profiles.json content from a blender_id folder in the
109 Blender config directory. If the file does not exist we create one with the
110 basic data structure.
112 import json
114 # if the file does not exist
115 if not os.path.exists(profiles_file):
116 return _create_default_file()
118 # try parsing the file
119 with open(profiles_file, 'r', encoding='utf8') as f:
120 try:
121 file_data = json.load(f)
122 file_data['active_profile']
123 file_data['profiles']
124 return file_data
125 except (ValueError, # malformed json data
126 KeyError): # it doesn't have the expected content
127 print('(%s) '
128 'Warning: profiles.json is either empty or malformed. '
129 'The file will be reset.' % __name__)
131 # overwrite the file
132 return _create_default_file()
135 def get_active_user_id():
136 """Get the id of the currently active profile. If there is no
137 active profile on the file, this function will return None.
140 return get_profiles_data()['active_profile']
143 def get_active_profile():
144 """Pick the active profile from profiles.json. If there is no
145 active profile on the file, this function will return None.
147 @returns: dict like {'user_id': 1234, 'username': 'email@blender.org'}
149 file_content = get_profiles_data()
150 user_id = file_content['active_profile']
151 if not user_id or user_id not in file_content['profiles']:
152 return None
154 profile = file_content['profiles'][user_id]
155 profile['user_id'] = user_id
156 return profile
159 def get_profile(user_id):
160 """Loads the profile data for a given user_id if existing
161 else it returns None.
164 file_content = get_profiles_data()
165 if not user_id or user_id not in file_content['profiles']:
166 return None
168 profile = file_content['profiles'][user_id]
169 return dict(
170 username=profile['username'],
171 token=profile['token']
175 def save_profiles_data(all_profiles: dict):
176 """Saves the profiles data to JSON."""
177 import json
179 with open(profiles_file, 'w', encoding='utf8') as outfile:
180 json.dump(all_profiles, outfile, sort_keys=True)
183 def save_as_active_profile(auth_result: communication.AuthResult, username, subclients):
184 """Saves the given info as the active profile."""
186 BlenderIdProfile.user_id = auth_result.user_id
187 BlenderIdProfile.token = auth_result.token
188 BlenderIdProfile.expires = auth_result.expires
190 BlenderIdProfile.username = username
191 BlenderIdProfile.subclients = subclients
193 BlenderIdProfile.save_json(make_active_profile=True)
196 def logout(user_id):
197 """Invalidates the token and state of active for this user.
198 This is different from switching the active profile, where the active
199 profile is changed but there isn't an explicit logout.
201 import json
203 file_content = get_profiles_data()
205 # Remove user from 'active profile'
206 if file_content['active_profile'] == user_id:
207 file_content['active_profile'] = ""
209 # Remove both user and token from profiles list
210 if user_id in file_content['profiles']:
211 del file_content['profiles'][user_id]
213 with open(profiles_file, 'w', encoding='utf8') as outfile:
214 json.dump(file_content, outfile)