Edouard@2339: #!/usr/bin/env python Edouard@2339: # -*- coding: utf-8 -*- Edouard@2339: Edouard@2339: # See COPYING file for copyrights details. Edouard@2339: Edouard@2339: from __future__ import absolute_import Edouard@2339: import os Edouard@2339: import time Edouard@2339: import json Edouard@2340: from zipfile import ZipFile Edouard@2339: Edouard@2340: # PSK Management Data model : Edouard@2340: # [[ID,Desc, LastKnownURI, LastConnect]] Edouard@2428: COL_ID, COL_URI, COL_DESC, COL_LAST = range(4) Edouard@2428: REPLACE, REPLACE_ALL, KEEP, KEEP_ALL, CANCEL = range(5) Edouard@2339: edouard@2492: Edouard@2339: def _pskpath(project_path): Edouard@2339: return os.path.join(project_path, 'psk') Edouard@2339: edouard@2492: Edouard@2339: def _mgtpath(project_path): Edouard@2339: return os.path.join(_pskpath(project_path), 'management.json') Edouard@2339: edouard@2492: Edouard@2461: def _ensurePSKdir(project_path): Edouard@2461: pskpath = _pskpath(project_path) Edouard@2461: if not os.path.exists(pskpath): Edouard@2461: os.mkdir(pskpath) Edouard@2461: return pskpath Edouard@2461: edouard@2492: Edouard@2340: def _default(ID): Edouard@2340: return [ID, edouard@2492: '', # default description edouard@2492: None, # last known URI Edouard@2339: None] # last connection date Edouard@2339: edouard@2492: Edouard@2340: def _dataByID(data): edouard@2492: return {row[COL_ID]: row for row in data} edouard@2492: Edouard@2340: Edouard@2339: def _LoadData(project_path): Edouard@2340: """ load known keys metadata """ Edouard@2339: if os.path.isdir(_pskpath(project_path)): Edouard@2339: _path = _mgtpath(project_path) Edouard@2340: if os.path.exists(_path): Edouard@2340: return json.loads(open(_path).read()) Edouard@2340: return [] Edouard@2339: edouard@2492: Edouard@2340: def _filterData(psk_files, data_input): Edouard@2340: input_by_ID = _dataByID(data_input) Edouard@2340: output = [] Edouard@2339: # go through all secret files available an build data Edouard@2339: # out of data recoverd from json and list of secret. Edouard@2339: # this implicitly filters IDs out of metadata who's Edouard@2339: # secret is missing Edouard@2339: for filename in psk_files: edouard@2492: if filename.endswith('.secret'): edouard@2492: ID = filename[:-7] # strip filename extension edouard@2492: output.append(input_by_ID.get(ID, _default(ID))) Edouard@2340: return output Edouard@2339: edouard@2492: Edouard@2340: def GetData(project_path): Edouard@2340: loaded_data = _LoadData(project_path) edouard@2429: if loaded_data: edouard@2429: psk_files = os.listdir(_pskpath(project_path)) edouard@2429: return _filterData(psk_files, loaded_data) edouard@2429: return [] Edouard@2339: edouard@2492: Edouard@2339: def DeleteID(project_path, ID): Edouard@2339: secret_path = os.path.join(_pskpath(project_path), ID+'.secret') Edouard@2339: os.remove(secret_path) Edouard@2339: edouard@2492: Edouard@2340: def SaveData(project_path, data): Edouard@2461: _ensurePSKdir(project_path) Edouard@2339: with open(_mgtpath(project_path), 'w') as f: Edouard@2339: f.write(json.dumps(data)) Edouard@2339: edouard@2492: Edouard@2339: def UpdateID(project_path, ID, secret, URI): Edouard@2461: pskpath = _ensurePSKdir(project_path) Edouard@2339: if not os.path.exists(pskpath): Edouard@2339: os.mkdir(pskpath) Edouard@2339: Edouard@2339: secpath = os.path.join(pskpath, ID+'.secret') Edouard@2339: with open(secpath, 'w') as f: Edouard@2339: f.write(ID+":"+secret) Edouard@2339: Edouard@2340: # here we directly use _LoadData, avoiding filtering that could be long Edouard@2339: data = _LoadData(project_path) Edouard@2340: idata = _dataByID(data) Edouard@2460: dataForID = idata.get(ID, None) if data else None Edouard@2460: Edouard@2460: _is_new_ID = dataForID is None Edouard@2460: if _is_new_ID: edouard@2492: dataForID = _default(ID) Edouard@2460: Edouard@2339: dataForID[COL_URI] = URI edouard@2492: # FIXME : could store time instead os a string and use DVC model's cmp Edouard@2339: # then date display could be smarter, etc - sortable sting hack for now Edouard@2339: dataForID[COL_LAST] = time.strftime('%y/%M/%d-%H:%M:%S') Edouard@2460: Edouard@2460: if _is_new_ID: Edouard@2460: data.append(dataForID) Edouard@2460: Edouard@2340: SaveData(project_path, data) Edouard@2340: edouard@2492: Edouard@2340: def ExportIDs(project_path, export_zip): Edouard@2340: with ZipFile(export_zip, 'w') as zf: Edouard@2340: path = _pskpath(project_path) Edouard@2340: for nm in os.listdir(path): Edouard@2340: if nm.endswith('.secret') or nm == 'management.json': Edouard@2340: zf.write(os.path.join(path, nm), nm) Edouard@2340: edouard@2492: Edouard@2340: def ImportIDs(project_path, import_zip, should_I_replace_callback): Edouard@2428: zf = ZipFile(import_zip, 'r') Edouard@2340: data = GetData(project_path) Edouard@2340: Edouard@2340: zip_loaded_data = json.loads(zf.open('management.json').read()) Edouard@2340: name_list = zf.namelist() Edouard@2428: zip_filtered_data = _filterData(name_list, zip_loaded_data) Edouard@2340: Edouard@2340: idata = _dataByID(data) Edouard@2340: Edouard@2428: keys_to_import = [] Edouard@2428: result = None Edouard@2428: Edouard@2340: for imported_row in zip_filtered_data: Edouard@2340: ID = imported_row[COL_ID] Edouard@2340: existing_row = idata.get(ID, None) Edouard@2340: if existing_row is None: Edouard@2340: data.append(imported_row) Edouard@2340: else: Edouard@2340: # callback returns the selected list for merge or none if canceled Edouard@2428: if result not in [REPLACE_ALL, KEEP_ALL]: Edouard@2428: result = should_I_replace_callback(existing_row, imported_row) Edouard@2428: Edouard@2428: if result == CANCEL: Edouard@2428: return edouard@2492: Edouard@2428: if result in [REPLACE_ALL, REPLACE]: Edouard@2340: # replace with imported Edouard@2340: existing_row[:] = imported_row Edouard@2340: # copy the key of selected Edouard@2428: keys_to_import.append(ID) edouard@2492: Edouard@2428: for ID in keys_to_import: Edouard@2428: zf.extract(ID+".secret", _pskpath(project_path)) Edouard@2340: Edouard@2340: SaveData(project_path, data) Edouard@2340: Edouard@2428: return data