Edouard@2339: #!/usr/bin/env python
Edouard@2339: # -*- coding: utf-8 -*-
Edouard@2339: 
Edouard@2339: # See COPYING file for copyrights details.
Edouard@2339: 
kinsamanka@3750: 
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]]
kinsamanka@3750: COL_ID, COL_URI, COL_DESC, COL_LAST = list(range(4))
kinsamanka@3750: REPLACE, REPLACE_ALL, KEEP, KEEP_ALL, CANCEL = list(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