def __init__(self, settings_file=PASSWORD_SETTINGS_FILE): self.settings_file = settings_file self.remote_data = None self.settings = [] self.sync_manager = SyncManager() self.update_remote = False
class PasswordSettingsManager: """ Use this class to manage password settings. It can save the settings locally to the settings file and it can export them to be sent to a sync server. :param settings_file: Filename of the settings file. Defaults to PASSWORD_SETTINGS_FILE as defined in the source :type settings_file: str """ def __init__(self, settings_file=PASSWORD_SETTINGS_FILE): self.settings_file = settings_file self.remote_data = None self.settings = [] self.sync_manager = SyncManager() self.update_remote = False def load_settings(self, password, update_from_sync=True, omit_sync_settings_questions=False): """ Loads settings from local file and from a sync server if possible. :param str password: masterpassword :param bool update_from_sync: do a sync update? :param bool omit_sync_settings_questions: do not ask for questions? (Defalut: False) :type password: str """ self.load_settings_from_file( password, not update_from_sync or omit_sync_settings_questions) if update_from_sync: self.update_from_sync(password) def load_settings_from_file(self, password, omit_sync_settings_questions=False): """ This loads the saved settings. It is a good idea to call this method the minute you have a password. :param str password: masterpassword :param bool omit_sync_settings_questions: do not ask for questions? (Defalut: False) :type password: str """ if os.path.isfile(self.settings_file): file = open(self.settings_file, 'br') data = file.read() crypter = Crypter(data[:32], password) sync_settings_len = struct.unpack('!I', data[32:36])[0] if sync_settings_len > 0: self.sync_manager.load_binary_sync_settings( crypter.decrypt(data[36:36 + sync_settings_len])) saved_settings = json.loads( str(Packer.decompress( crypter.decrypt(data[36 + sync_settings_len:])), encoding='utf-8')) for domain_name in saved_settings['settings'].keys(): data_set = saved_settings['settings'][domain_name] found = False i = 0 while i < len(self.settings): setting = self.settings[i] if setting.get_domain() == domain_name: found = True if datetime.strptime( data_set['mDate'], "%Y-%m-%dT%H:%M:%S") > setting.get_m_date(): setting.load_from_dict(data_set) setting.set_synced(setting.get_domain() in saved_settings['synced']) i += 1 if not found: new_setting = PasswordSetting(domain_name) new_setting.load_from_dict(data_set) new_setting.set_synced( new_setting.get_domain() in saved_settings['synced']) self.settings.append(new_setting) file.close() else: if not omit_sync_settings_questions: self.sync_manager.ask_for_sync_settings() def store_settings(self, password): """ Stores settings locally and remotely. :param password: masterpassword :type password: str :return: """ self.save_settings_to_file(password) self.update_sync_server_if_necessary(password) # noinspection PyUnresolvedReferences def save_settings_to_file(self, password): """ This actually saves the settings to a file on the disk. The file is encrypted so you need to supply the password. :param password: masterpassword :type password: str """ salt = os.urandom(32) crypter = Crypter(salt, password) file = open(self.settings_file, 'bw') encrypted_sync_settings = crypter.encrypt( self.sync_manager.get_binary_sync_settings()) file.write( salt + struct.pack('!I', len(encrypted_sync_settings)) + encrypted_sync_settings + crypter.encrypt( Packer.compress(json.dumps(self.get_settings_as_dict())))) file.close() try: import win32con import win32api win32api.SetFileAttributes(self.settings_file, win32con.FILE_ATTRIBUTE_HIDDEN) except ImportError: pass def update_sync_server_if_necessary(self, password): """ Checks if the sync server needs to be updated. If necessary it does a push. :param password: masterpassword :type password: str """ if self.update_remote: if self.sync_manager.push(self.get_export_data(password)): self.set_all_settings_to_synced() def get_setting(self, domain): """ This function always returns a setting. If no setting was stored for the given domain a new PasswordSetting object is created. :param domain: The "domain" is the identifier of a settings object. :type domain: str :return: a setting object :rtype: PasswordSetting """ for setting in self.settings: if setting.get_domain() == domain: return setting setting = PasswordSetting(domain) self.settings.append(setting) return setting def set_setting(self, setting): """ This saves the supplied setting only in memory. Call save_settings_to_file if you want to have it saved to disk. :param PasswordSetting setting: the setting which should be saved """ for i, existing_setting in enumerate(self.settings): if existing_setting.get_domain() == setting.get_domain(): self.settings.pop(i) self.settings.append(setting) self.update_remote = True def delete_setting(self, setting): """ This removes the setting from the internal list. Call save_settings_to_file if you want to have the change saved to disk. :param setting: PasswordSetting object :type setting: PasswordSetting """ i = 0 while i < len(self.settings): existing_setting = self.settings[i] if existing_setting.get_domain() == setting.get_domain(): self.settings.pop(i) else: i += 1 def get_domain_list(self): """ This gives you a list of saved domains. :return: a list of domain names :rtype: [str] """ return [setting.get_domain() for setting in self.settings] def get_settings_as_dict(self): """ Constructs a dictionary with a list of settings (no PasswordSetting objects but dicts) and a list of domain names of synced domains. :return: a dictionary :rtype: dict """ settings_list = {'settings': {}, 'synced': []} for setting in self.settings: settings_list['settings'][setting.get_domain()] = setting.to_dict() if setting.is_synced(): settings_list['synced'].append(setting.get_domain()) return settings_list def get_export_data(self, password, salt=None): """ This gives you a base64 encoded string of encrypted settings data (the blob). :param password: masterpassword :type password: str :param salt: salt for the encryption: This is for testing only! Do not set it normally! :type salt: bytes :return: encrypted settings blob :rtype: str """ settings_list = self.get_settings_as_dict()['settings'] if self.remote_data: for domain_name in self.remote_data.keys(): data_set = self.remote_data[domain_name] if 'deleted' in data_set and data_set['deleted']: for i, setting_dict in enumerate(settings_list): if setting_dict['domain'] == setting_dict[ 'domain'] and datetime.strptime( data_set['mDate'], "%Y-%m-%dT%H:%M:%S") > datetime.strptime( setting_dict['mDate'], "%Y-%m-%dT%H:%M:%S"): settings_list[i] = data_set if domain_name not in settings_list.keys(): settings_list[domain_name] = { 'mDate': datetime.now(), 'deleted': True } if not salt: salt = os.urandom(32) crypter = Crypter(salt, password) return b64encode( b'\x00' + salt + crypter.encrypt(Packer.compress(json.dumps(settings_list)))) def update_from_sync(self, password): """ Call this method to pull settings from the sync server. :param password: the masterpassword :type password: str """ pull_successful, data = self.sync_manager.pull() if not pull_successful: print("Sync failed: No connection to the server.") return False if not len(data) > 0: return False binary_data = b64decode(data) data_version = binary_data[:1] if data_version == b'\x00': encryption_salt = binary_data[1:33] encrypted_data = binary_data[33:] crypter = Crypter(encryption_salt, password) self.remote_data = json.loads( str(Packer.decompress(crypter.decrypt(encrypted_data)), encoding='utf-8')) self.update_remote = False for domain_name in self.remote_data.keys(): data_set = self.remote_data[domain_name] found = False i = 0 while i < len(self.settings): setting = self.settings[i] if setting.get_domain() == domain_name: found = True if datetime.strptime( data_set['mDate'], "%Y-%m-%dT%H:%M:%S") > setting.get_m_date(): if 'deleted' in data_set and data_set['deleted']: self.settings.pop(i) else: setting.load_from_dict(data_set) setting.set_synced(True) self.update_remote = True i += 1 else: i += 1 else: i += 1 if not found: new_setting = PasswordSetting(domain_name) new_setting.load_from_dict(data_set) new_setting.set_synced(True) self.settings.append(new_setting) for setting in self.settings: found = False for domain_name in self.remote_data.keys(): data_set = self.remote_data[domain_name] if setting.get_domain() == domain_name: found = True if setting.get_m_date() >= datetime.strptime( data_set['mDate'], "%Y-%m-%dT%H:%M:%S"): self.update_remote = True if not found: self.update_remote = True else: print("Unknown data format version! Could not update.") def set_all_settings_to_synced(self): """ Convenience function for marking all saved settings as synced. Call this after a successful update at the sync server. """ for setting in self.settings: setting.set_synced(True)
class PasswordSettingsManager: """ Use this class to manage password settings. It can save the settings locally to the settings file and it can export them to be sent to a sync server. :param settings_file: Filename of the settings file. Defaults to PASSWORD_SETTINGS_FILE as defined in the source :type settings_file: str """ def __init__(self, settings_file=PASSWORD_SETTINGS_FILE): self.settings_file = settings_file self.remote_data = None self.settings = [] self.sync_manager = SyncManager() self.update_remote = False def load_settings(self, password, update_from_sync=True, omit_sync_settings_questions=False): """ Loads settings from local file and from a sync server if possible. :param str password: masterpassword :param bool update_from_sync: do a sync update? :param bool omit_sync_settings_questions: do not ask for questions? (Defalut: False) :type password: str """ self.load_settings_from_file(password, not update_from_sync or omit_sync_settings_questions) if update_from_sync: self.update_from_sync(password) def load_settings_from_file(self, password, omit_sync_settings_questions=False): """ This loads the saved settings. It is a good idea to call this method the minute you have a password. :param str password: masterpassword :param bool omit_sync_settings_questions: do not ask for questions? (Defalut: False) :type password: str """ if os.path.isfile(self.settings_file): file = open(self.settings_file, 'br') data = file.read() crypter = Crypter(data[:32], password) sync_settings_len = struct.unpack('!I', data[32:36])[0] if sync_settings_len > 0: self.sync_manager.load_binary_sync_settings(crypter.decrypt(data[36:36+sync_settings_len])) saved_settings = json.loads(str(Packer.decompress(crypter.decrypt(data[36+sync_settings_len:])), encoding='utf-8')) for domain_name in saved_settings['settings'].keys(): data_set = saved_settings['settings'][domain_name] found = False i = 0 while i < len(self.settings): setting = self.settings[i] if setting.get_domain() == domain_name: found = True if datetime.strptime(data_set['mDate'], "%Y-%m-%dT%H:%M:%S") > setting.get_m_date(): setting.load_from_dict(data_set) setting.set_synced(setting.get_domain() in saved_settings['synced']) i += 1 if not found: new_setting = PasswordSetting(domain_name) new_setting.load_from_dict(data_set) new_setting.set_synced(new_setting.get_domain() in saved_settings['synced']) self.settings.append(new_setting) file.close() else: if not omit_sync_settings_questions: self.sync_manager.ask_for_sync_settings() def store_settings(self, password): """ Stores settings locally and remotely. :param password: masterpassword :type password: str :return: """ self.save_settings_to_file(password) self.update_sync_server_if_necessary(password) # noinspection PyUnresolvedReferences def save_settings_to_file(self, password): """ This actually saves the settings to a file on the disk. The file is encrypted so you need to supply the password. :param password: masterpassword :type password: str """ salt = os.urandom(32) crypter = Crypter(salt, password) file = open(self.settings_file, 'bw') encrypted_sync_settings = crypter.encrypt(self.sync_manager.get_binary_sync_settings()) file.write(salt + struct.pack('!I', len(encrypted_sync_settings)) + encrypted_sync_settings + crypter.encrypt(Packer.compress(json.dumps(self.get_settings_as_dict())))) file.close() try: import win32con import win32api win32api.SetFileAttributes(self.settings_file, win32con.FILE_ATTRIBUTE_HIDDEN) except ImportError: pass def update_sync_server_if_necessary(self, password): """ Checks if the sync server needs to be updated. If necessary it does a push. :param password: masterpassword :type password: str """ if self.update_remote: if self.sync_manager.push(self.get_export_data(password)): self.set_all_settings_to_synced() def get_setting(self, domain): """ This function always returns a setting. If no setting was stored for the given domain a new PasswordSetting object is created. :param domain: The "domain" is the identifier of a settings object. :type domain: str :return: a setting object :rtype: PasswordSetting """ for setting in self.settings: if setting.get_domain() == domain: return setting setting = PasswordSetting(domain) self.settings.append(setting) return setting def set_setting(self, setting): """ This saves the supplied setting only in memory. Call save_settings_to_file if you want to have it saved to disk. :param PasswordSetting setting: the setting which should be saved """ for i, existing_setting in enumerate(self.settings): if existing_setting.get_domain() == setting.get_domain(): self.settings.pop(i) self.settings.append(setting) self.update_remote = True def delete_setting(self, setting): """ This removes the setting from the internal list. Call save_settings_to_file if you want to have the change saved to disk. :param setting: PasswordSetting object :type setting: PasswordSetting """ i = 0 while i < len(self.settings): existing_setting = self.settings[i] if existing_setting.get_domain() == setting.get_domain(): self.settings.pop(i) else: i += 1 def get_domain_list(self): """ This gives you a list of saved domains. :return: a list of domain names :rtype: [str] """ return [setting.get_domain() for setting in self.settings] def get_settings_as_dict(self): """ Constructs a dictionary with a list of settings (no PasswordSetting objects but dicts) and a list of domain names of synced domains. :return: a dictionary :rtype: dict """ settings_list = {'settings': {}, 'synced': []} for setting in self.settings: settings_list['settings'][setting.get_domain()] = setting.to_dict() if setting.is_synced(): settings_list['synced'].append(setting.get_domain()) return settings_list def get_export_data(self, password, salt=None): """ This gives you a base64 encoded string of encrypted settings data (the blob). :param password: masterpassword :type password: str :param salt: salt for the encryption: This is for testing only! Do not set it normally! :type salt: bytes :return: encrypted settings blob :rtype: str """ settings_list = self.get_settings_as_dict()['settings'] if self.remote_data: for domain_name in self.remote_data.keys(): data_set = self.remote_data[domain_name] if 'deleted' in data_set and data_set['deleted']: for i, setting_dict in enumerate(settings_list): if setting_dict['domain'] == setting_dict['domain'] and datetime.strptime( data_set['mDate'], "%Y-%m-%dT%H:%M:%S") > datetime.strptime( setting_dict['mDate'], "%Y-%m-%dT%H:%M:%S"): settings_list[i] = data_set if domain_name not in settings_list.keys(): settings_list[domain_name] = { 'mDate': datetime.now(), 'deleted': True } if not salt: salt = os.urandom(32) crypter = Crypter(salt, password) return b64encode(b'\x00' + salt + crypter.encrypt(Packer.compress(json.dumps(settings_list)))) def update_from_sync(self, password): """ Call this method to pull settings from the sync server. :param password: the masterpassword :type password: str """ pull_successful, data = self.sync_manager.pull() if not pull_successful: print("Sync failed: No connection to the server.") return False if not len(data) > 0: return False binary_data = b64decode(data) data_version = binary_data[:1] if data_version == b'\x00': encryption_salt = binary_data[1:33] encrypted_data = binary_data[33:] crypter = Crypter(encryption_salt, password) self.remote_data = json.loads( str(Packer.decompress(crypter.decrypt(encrypted_data)), encoding='utf-8')) self.update_remote = False for domain_name in self.remote_data.keys(): data_set = self.remote_data[domain_name] found = False i = 0 while i < len(self.settings): setting = self.settings[i] if setting.get_domain() == domain_name: found = True if datetime.strptime(data_set['mDate'], "%Y-%m-%dT%H:%M:%S") > setting.get_m_date(): if 'deleted' in data_set and data_set['deleted']: self.settings.pop(i) else: setting.load_from_dict(data_set) setting.set_synced(True) self.update_remote = True i += 1 else: i += 1 else: i += 1 if not found: new_setting = PasswordSetting(domain_name) new_setting.load_from_dict(data_set) new_setting.set_synced(True) self.settings.append(new_setting) for setting in self.settings: found = False for domain_name in self.remote_data.keys(): data_set = self.remote_data[domain_name] if setting.get_domain() == domain_name: found = True if setting.get_m_date() >= datetime.strptime(data_set['mDate'], "%Y-%m-%dT%H:%M:%S"): self.update_remote = True if not found: self.update_remote = True else: print("Unknown data format version! Could not update.") def set_all_settings_to_synced(self): """ Convenience function for marking all saved settings as synced. Call this after a successful update at the sync server. """ for setting in self.settings: setting.set_synced(True)
from App import App from MusicManager import MusicManager from SyncManager import SyncManager app = App() session = app.auth_user() # authorise in account manager = MusicManager(session) songs = manager.get_audio_list() sync = SyncManager(songs) sync.chache_in_db() sync.download_audio()