# HG changeset patch # User Edouard Tisserant # Date 1542805851 -3600 # Node ID decf52efb7f7b0daa00b6d19bf37c40162baccb9 # Parent 48b4eba1306406cee838bd224b5fb65a668c27ac IDManager: added import/export plus little cosmetic enhancements. diff -r 48b4eba13064 -r decf52efb7f7 PSKManagement.py --- a/PSKManagement.py Tue Nov 20 11:32:42 2018 +0100 +++ b/PSKManagement.py Wed Nov 21 14:10:51 2018 +0100 @@ -7,7 +7,10 @@ import os import time import json +from zipfile import ZipFile +# PSK Management Data model : +# [[ID,Desc, LastKnownURI, LastConnect]] COL_ID,COL_URI,COL_DESC,COL_LAST = range(4) def _pskpath(project_path): @@ -16,51 +19,52 @@ def _mgtpath(project_path): return os.path.join(_pskpath(project_path), 'management.json') -def _default(): - return ['', # default description +def _default(ID): + return [ID, + '', # default description None, # last known URI None] # last connection date +def _dataByID(data): + return {row[COL_ID]:row for row in data} + def _LoadData(project_path): + """ load known keys metadata """ if os.path.isdir(_pskpath(project_path)): _path = _mgtpath(project_path) - # load known keys metadata - # {ID:(Desc, LastKnownURI, LastConnect)} - return json.loads(open(_path).read()) \ - if os.path.exists(_path) else {} - return {} + if os.path.exists(_path): + return json.loads(open(_path).read()) + return [] -def GetData(project_path): - # [(ID, Desc, LastKnownURI, LastConnect) - data = [] - loaded_data = _LoadData(project_path) +def _filterData(psk_files, data_input): + input_by_ID = _dataByID(data_input) + output = [] # go through all secret files available an build data # out of data recoverd from json and list of secret. # this implicitly filters IDs out of metadata who's # secret is missing - psk_files = os.listdir(_pskpath(project_path)) for filename in psk_files: if filename.endswith('.secret'): ID = filename[:-7] # strip filename extension - meta = loaded_data.get(ID,_default()) - data.append([ID]+meta) - return data + output.append(input_by_ID.get(ID,_default(ID))) + return output +def GetData(project_path): + loaded_data = _LoadData(project_path) + psk_files = os.listdir(_pskpath(project_path)) + return _filterData(psk_files, loaded_data) def DeleteID(project_path, ID): secret_path = os.path.join(_pskpath(project_path), ID+'.secret') os.remove(secret_path) -def _StoreData(project_path, data): +def SaveData(project_path, data): pskpath = _pskpath(project_path) if not os.path.isdir(pskpath): os.mkdir(pskpath) with open(_mgtpath(project_path), 'w') as f: f.write(json.dumps(data)) -def SaveData(project_path, data): - to_store = {row[0]:row[1:] for row in data} - _StoreData(project_path, to_store) def UpdateID(project_path, ID, secret, URI): pskpath = _pskpath(project_path) @@ -71,11 +75,49 @@ with open(secpath, 'w') as f: f.write(ID+":"+secret) + # here we directly use _LoadData, avoiding filtering that could be long data = _LoadData(project_path) - dataForID = [ID] + (data.get(ID, _default()) if data else _default()) + idata = _dataByID(data) + dataForID = idata.get(ID, _default(ID)) if data else _default(ID) dataForID[COL_URI] = URI # FIXME : could store time instead os a string and use DVC model's cmp # then date display could be smarter, etc - sortable sting hack for now dataForID[COL_LAST] = time.strftime('%y/%M/%d-%H:%M:%S') - data[ID] = dataForID[1:] - _StoreData(project_path, data) + SaveData(project_path, data) + +def ExportIDs(project_path, export_zip): + with ZipFile(export_zip, 'w') as zf: + path = _pskpath(project_path) + for nm in os.listdir(path): + if nm.endswith('.secret') or nm == 'management.json': + zf.write(os.path.join(path, nm), nm) + +def ImportIDs(project_path, import_zip, should_I_replace_callback): + data = GetData(project_path) + + zip_loaded_data = json.loads(zf.open('management.json').read()) + name_list = zf.namelist() + zip_filtered_data = _filterData(name_list, loaded_data) + + idata = _dataByID(data) + + for imported_row in zip_filtered_data: + ID = imported_row[COL_ID] + existing_row = idata.get(ID, None) + if existing_row is None: + data.append(imported_row) + else: + # callback returns the selected list for merge or none if canceled + result = should_I_replace_callback(existing_row, imported_row) + if result is None: + break + + if result: + # replace with imported + existing_row[:] = imported_row + # copy the key of selected + self.extract(ID+".secret", _pskpath(project_path)) + + SaveData(project_path, data) + + diff -r 48b4eba13064 -r decf52efb7f7 controls/IDBrowser.py --- a/controls/IDBrowser.py Tue Nov 20 11:32:42 2018 +0100 +++ b/controls/IDBrowser.py Wed Nov 21 14:10:51 2018 +0100 @@ -11,14 +11,14 @@ from PSKManagement import COL_ID,COL_URI,COL_DESC,COL_LAST class IDBrowserModel(dv.PyDataViewIndexListModel): - def __init__(self, psk_path, columncount): - self.psk_path = psk_path + def __init__(self, project_path, columncount): + self.project_path = project_path self.columncount = columncount - self.data = PSK.GetData(psk_path) + self.data = PSK.GetData(project_path) dv.PyDataViewIndexListModel.__init__(self, len(self.data)) def _saveData(self): - PSK.SaveData(self.psk_path, self.data) + PSK.SaveData(self.project_path, self.data) def GetColumnType(self, col): return "string" @@ -59,7 +59,7 @@ rows.sort(reverse=True) for row in rows: - PSK.DeleteID(self.psk_path, self.data[row][COL_ID]) + PSK.DeleteID(self.project_path, self.data[row][COL_ID]) del self.data[row] self.RowDeleted(row) self._saveData() @@ -69,13 +69,20 @@ self.RowAppended() self._saveData() + def Import(self, filepath, sircb): + PSK.ImportIDs(self.project_path, filepath, sircb) + + def Export(self, filepath): + PSK.ExportIDs(self.project_path, filepath) + colflags = dv.DATAVIEW_COL_RESIZABLE|dv.DATAVIEW_COL_SORTABLE class IDBrowser(wx.Panel): def __init__(self, parent, ctr, SelectURICallBack=None, SelectIDCallBack=None, **kwargs): - wx.Panel.__init__(self, parent, -1, size=(400,200)) + big = self.isManager = SelectURICallBack is None and SelectIDCallBack is None + wx.Panel.__init__(self, parent, -1, size=(800 if big else 400, + 600 if big else 200)) - self.isManager = SelectURICallBack is None and SelectIDCallBack is None self.SelectURICallBack = SelectURICallBack self.SelectIDCallBack = SelectIDCallBack @@ -89,12 +96,12 @@ ColumnsDesc = [ args(_("ID"), COL_ID, width = 100), - args(_("Last URI"), COL_URI, width = 80), + args(_("Last URI"), COL_URI, width = 160 if big else 80), args(_("Description"), COL_DESC, width = 200, mode = dv.DATAVIEW_CELL_EDITABLE if self.isManager else dv.DATAVIEW_CELL_INERT), - args(_("Last connection"), COL_LAST, width = 100), + args(_("Last connection"), COL_LAST, width = 120), ] self.model = IDBrowserModel(ctr.ProjectPath, len(ColumnsDesc)) @@ -171,14 +178,22 @@ self.SelectURICallBack(URI) def OnExportButton(self, evt): - # TODO - wx.MessageBox(_('?'), - _('Mhe'), - wx.YES_NO | wx.CENTRE | wx.NO_DEFAULT) + dialog = wx.FileDialog(self, _("Choose a file"), + wildcard = _("PSK ZIP files (*.zip)|*.zip"), + style = wx.SAVE | wx.OVERWRITE_PROMPT) + if dialog.ShowModal() == wx.ID_OK: + self.model.Export(dialog.GetPath()) + + def ShouldIReplaceCallback(self,some,stuff): + # TODO + wx.MessageBox("TODO : ShouldIReplaceCallback") + return True def OnImportButton(self, evt): - # TODO - wx.MessageBox(_('?'), - _('Mhe'), - wx.YES_NO | wx.CENTRE | wx.NO_DEFAULT) + dialog = wx.FileDialog(self, _("Choose a file"), + wildcard = _("PSK ZIP files (*.zip)|*.zip"), + style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if dialog.ShowModal() == wx.ID_OK: + self.model.Import(dialog.GetPath(), + self.ShouldIReplaceCallback)