diff --git a/forms.py b/forms.py index 45aeaef..919ae69 100644 --- a/forms.py +++ b/forms.py @@ -4,11 +4,12 @@ import wx import wx.gizmos import wx.propgrid as wxpg -ID_TEST = wx.NewId() -ID_DUPLICATE = wx.NewId() -ID_DUPLICATE_MODEL = wx.NewId() -ID_DELETE_MODEL = wx.NewId() -ID_PROCESS_MODEL = wx.NewId() +ID_TEST = wx.NewId() +ID_ADD_MODEL_ROOT = wx.NewId() +ID_ADD_MODEL_SELECTED = wx.NewId() +ID_DUPLICATE_MODEL = wx.NewId() +ID_DELETE_MODEL = wx.NewId() +ID_PROCESS_MODEL = wx.NewId() class MyTreeListCtrl(wx.gizmos.TreeListCtrl): def Refresh(self, erase, rect): @@ -23,7 +24,7 @@ class MainFrame (wx.Frame): bSizer3 = wx.BoxSizer(wx.HORIZONTAL) - self.m_specs = wx.TreeCtrl(self, style = wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT) + self.m_specs = wx.TreeCtrl(self, style = wx.TR_DEFAULT_STYLE) self.m_specs.SetMinSize(wx.Size(150,-1)) bSizer3.Add(self.m_specs, 0, wx.ALL|wx.EXPAND, 1) @@ -33,11 +34,13 @@ class MainFrame (wx.Frame): self.m_user_models = wx.gizmos.TreeListCtrl(self, #self.m_user_models = MyTreeListCtrl(self, #self.m_user_models = wx.TreeCtrl(self, - style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.TR_EDIT_LABELS | wx.TR_ROW_LINES) + style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT + | wx.TR_EDIT_LABELS | wx.TR_ROW_LINES | wx.TR_MULTIPLE) self.m_user_models.SetMinSize(wx.Size(-1, 200)) self.m_user_models.AddColumn("Model name") self.m_user_models.AddColumn("Status") self.m_user_models.AddColumn("Progress") + self.m_user_models.AddColumn("Comment") bSizer4.Add(self.m_user_models, 0, wx.ALL|wx.EXPAND, 1) @@ -59,6 +62,7 @@ class MainFrame (wx.Frame): mbar = self.BuildMenu() self.SetMenuBar(mbar) + self.BuildContextMenu() self.SetSizer(bSizer3) self.Layout() @@ -76,8 +80,14 @@ class MainFrame (wx.Frame): menubar.Append(menu, '&File') menu = wx.Menu() - menu.Append(ID_TEST, "&Test\tCtrl+U") - menu.Append(ID_DUPLICATE, "&Duplicate\tCtrl+D") + menu.Append(ID_TEST, "&Test\tCtrl+T") + menu.Append(ID_ADD_MODEL_ROOT, 'Add model to root') + menu.Append(ID_ADD_MODEL_SELECTED, 'Append model to selected') + #menu.AppendSeparator() + menu.Append(ID_DUPLICATE_MODEL, "&Duplicate\tCtrl+D") + menu.Append(ID_DELETE_MODEL, 'Delete') + menu.AppendSeparator() + menu.Append(ID_PROCESS_MODEL, 'Process\tCtrl+R') menubar.Append(menu, '&Model') menu = wx.Menu() @@ -86,3 +96,11 @@ class MainFrame (wx.Frame): menubar.Append(menu, '&Help') return menubar + + def BuildContextMenu(self): + + menu = wx.Menu() + menu.Append(ID_ADD_MODEL_ROOT, 'Add model to root') + menu.Append(ID_ADD_MODEL_SELECTED, 'Add model to selected') + self.m_specs.Bind(wx.EVT_CONTEXT_MENU, + lambda x: self.m_specs.PopupMenu(menu)) diff --git a/opal.py b/opal.py index 1b59d20..bd458e6 100644 --- a/opal.py +++ b/opal.py @@ -20,6 +20,7 @@ import time import datetime import os import threading +import re #----------------------------------------------------------------------------- # Главная форма @@ -29,6 +30,8 @@ class MainFrame(forms.MainFrame): def __init__(self): forms.MainFrame.__init__(self, None) + self.name_id = 1 + s = server.LocalServer() s.LoadModels() models = s.GetModels() @@ -45,7 +48,14 @@ class MainFrame(forms.MainFrame): self.OnParamChanged) self.Bind(wx.EVT_MENU, self.OnTest, id = forms.ID_TEST) - self.Bind(wx.EVT_MENU, self.OnDuplicate, id = forms.ID_DUPLICATE) + self.Bind(wx.EVT_MENU, self.OnAddModelToRoot, + id = forms.ID_ADD_MODEL_ROOT) + self.Bind(wx.EVT_MENU, self.OnAddModelToSelected, + id = forms.ID_ADD_MODEL_SELECTED) + self.Bind(wx.EVT_MENU, self.OnDuplicate, + id = forms.ID_DUPLICATE_MODEL) + self.Bind(wx.EVT_MENU, self.OnModelProcess, + id = forms.ID_PROCESS_MODEL) self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(wx.EVT_IDLE, self.OnIdle) @@ -62,45 +72,127 @@ class MainFrame(forms.MainFrame): self.Destroy() def Overseer(self): + + def StateToStr(state): + if state == server.JOB_READY: + return 'Ready' + elif state == server.JOB_RUNNING: + return 'Running' + elif state == server.JOB_STOPPED: + return 'Stopped' + elif state == server.JOB_COMPLETED: + return 'Completed' + else: + return 'Unknown' + try: um = self.m_user_models cycle_count = 0 while True: wx.MutexGuiEnter() - print 'cycle {:-8} '.format(cycle_count) + print 'cycle{:-8}'.format(cycle_count) cycle_count += 1 item = um.GetRootItem() while item.IsOk(): data = um.GetPyData(item) if data: jid = data[1] - if jid != None and self.server.IsJobChanged(jid): - tid = self.server.GetJobTID(jid) - meta = self.server.GetTaskMeta(tid) - t = os.path.basename(meta['exec']) - state = self.server.GetJobState(jid) - um.SetItemText(item, str(state[0]), 1) - um.SetItemText(item, '{}: {:%}'.format(t, state[1]), 2) + state = self.server.GetJobState(jid) + um.SetItemText(item, StateToStr(state[0]), 1) + p = state[1] + p = 'Unknown' if p < 0 else '{:%}'.format(p) + um.SetItemText(item, p, 2) + um.SetItemText(item, state[2], 3) print jid, state item = um.GetNext(item) wx.MutexGuiLeave() - time.sleep(0.1) + time.sleep(0.2) except Exception, e: print 'Error in overseer: ', e - def NewProject(self, project): + def CheckName(self, name): + """ + Проверяет имя на уникальность в иерархии пользовательских моделей. + Возвращает True, если имя уникально, иначе False. + """ + um = self.m_user_models + item = um.GetRootItem() + while item.IsOk(): + item_name = um.GetItemText(item) + if item_name == name: + return False + item = um.GetNext(item) + return True + + def GenerateName(self, name): + """ + На основе переданного имени генерирует новое имя модели таким образом, + чтобы оно осталось уникальным в рамках существующей иерархии моделей. + """ + m = re.match(r'(.+)\s+\d*', name) + basename = m.group(1) if m else name + while True: + name = basename + ' ' + str(self.name_id) + if self.CheckName(name): + return name + self.name_id += 1 + + def BuildSpecs(self, model): + """ + Выстраивает иерархию спецификаций для выбранной модели + """ + def DoItem(item, model): + sp.SetPyData(item, model) + for spec in model.GetSpecs(): + child = sp.AppendItem(item, spec.GetTitle()) + DoItem(child, spec) + + sp = self.m_specs + sp.DeleteAllItems() + root = sp.AddRoot(model.GetTitle()) + DoItem(root, model) + sp.ExpandAll() + sp.SortChildren(root) + + def AddModelToRoot(self, model): + """ + Добавляет пользовательскую модель или спецификацию + в корень дерева моделей. + """ + + ms = [] + while model: + ms.append(model) + model = model.GetParent() + ms.reverse() + # ms: [root-model, child, child-of-child1, ..., model] + + um = self.m_user_models + item = um.GetRootItem() + defparent = None + for m in ms: + name = self.GenerateName(m.GetTitle()) + item = um.AppendItem(item, name) + data = task.DataDefinition(m, defparent) + defparent = data + jid = self.server.CreateJob() if m.IsExecutable() else None + um.SetPyData(item, [data, jid]) + + def NewProject(self, model): # 1. загрузить спецификации модели # 2. создать одну модель по умолчанию - model = project - um = self.m_user_models - root = um.AddRoot('Root') - data = task.DataDefinition(model) - child = um.AppendItem(root, 'Default') - jid = self.server.CreateJob() - um.SetPyData(child, [data, jid]) + self.BuildSpecs(model) + + um = self.m_user_models + um.DeleteAllItems() + um.AddRoot('Root') + + self.AddModelToRoot(model) + + return True # Project(model) def SelectUserModel(self, model_def, jid): @@ -165,12 +257,41 @@ class MainFrame(forms.MainFrame): data[param] = value def OnTest(self, event): + um = self.m_user_models + + def OnAddModelToRoot(self, event): + item = self.m_specs.GetSelection() + if not item.IsOk(): + return + print self.m_specs.GetItemText(item) + model = self.m_specs.GetPyData(item) + self.AddModelToRoot(model) + + def OnAddModelToSelected(self, event): + """ + Добавляет пользовательскую спецификацию к указанной модели или в уже + существующую иерархию спецификаций. + """ + item = self.m_specs.GetSelection() + if not item.IsOk(): + return + model = self.m_specs.GetPyData(item) um = self.m_user_models - id = um.GetSelection() - data, jid = um.GetItemPyData(id) + item = um.GetSelection() + if not item.IsOk(): + return - self.server.LaunchJob(jid, data) + pmdef, _ = um.GetPyData(item) + + if pmdef.DD == model.parent: + modeldef = task.DataDefinition(model, pmdef) + name = self.GenerateName(model.GetTitle()) + item = um.AppendItem(item, name) + jid = self.server.CreateJob() if model.IsExecutable() else None + um.SetPyData(item, [modeldef, jid]) + else: + wx.MessageBox('It\'s impossible to append model', 'Error') def OnDuplicate(self, event): um = self.m_user_models @@ -178,11 +299,18 @@ class MainFrame(forms.MainFrame): title = um.GetItemText(id) parent = um.GetItemParent(id) md, jid = um.GetItemPyData(id) - child = um.AppendItem(parent, title + ' Copy') + + child = um.AppendItem(parent, self.GenerateName(title)) jid = self.server.CreateJob() um.SetPyData(child, [md.Copy(), jid]) self.SetStatusText('Copy for "{}" created'.format(title), 0) + def OnModelProcess(self, event): + um = self.m_user_models + for i in um.GetSelections(): + data, jid = um.GetItemPyData(i) + self.server.LaunchJob(jid, data) + def OnIdle(self, event): pass diff --git a/server.py b/server.py index f5314da..e0b892e 100644 --- a/server.py +++ b/server.py @@ -30,6 +30,9 @@ def WriteToLog(msg): #self.log.write(msg + '\n') print msg +class JIDError(Exception): + def __str__(self): + return 'Invalid jid' class LocalServer: def __init__(self, conf = 'tasks.conf', workers = 2): @@ -127,6 +130,9 @@ class LocalServer: def GetJobsCount(self): return len(self.jobs) + #def CheckJID(self, func): + # def + def GetJobState(self, jid): job = self.jobs.get(jid) if job != None: @@ -302,6 +308,7 @@ class Job: self.tid = tid self.datadump = datadump self.state = JOB_READY + self.percent = -1.0 self.ChangeState() def Stop(self): diff --git a/task.py b/task.py index dbf35d0..0da6b33 100644 --- a/task.py +++ b/task.py @@ -76,7 +76,10 @@ class DataDescription: def GetId(self): return None - def GetSpecifications(self): + def GetParent(self): + return self.parent + + def GetSpecs(self): return self.specs def IsExecutable(self): diff --git a/tasks/testt.json b/tasks/testt.json index d67d7a1..d400240 100644 --- a/tasks/testt.json +++ b/tasks/testt.json @@ -1,13 +1,13 @@ { "title": "Example task", "author": "Anton Vakhrushev", - "meta": "av-example-task", + "meta": "av-example-task-version-00-10", "models": { "sintaylor": { - "title": "Simple model for example", + "title": "Simple model", "author": "Anton Vakhrushev", "date": "2012-03-08", @@ -31,10 +31,25 @@ "n": { "type": "int", - "default": 10, + "default": 100, "title": "Steps", "comment": "Number of steps for algorithm" } + }, + + "spec": { + + "left": { + "title": "Left" + }, + + "right": { + "title": "Right" + }, + + "trapezium": { + "title": "Trapezium" + } } } } diff --git a/tasks/testt.py b/tasks/testt.py index cfc15c7..cb3e609 100644 --- a/tasks/testt.py +++ b/tasks/testt.py @@ -26,10 +26,11 @@ def sin_taylor(x, n): e *= -1 return s -def answer(p): +def answer(p, c = ''): return json.dumps({ "answer": "ok", - "value": p + "value": p, + "comment": c }) def error(msg): @@ -61,7 +62,7 @@ def main(): textdata = raw_input() data = json.loads(textdata) - if not len(data) and data[0]['label'] != 'sintaylor': + if not len(data) or data[-1]['label'] != 'sintaylor': write(error('Unknown model')) sys.exit(1) @@ -76,9 +77,9 @@ def main(): while l <= r: y = sin_taylor(l, d) res.append([l, y]) - write(answer(l / r)) + write(answer(l / r, data[-1]['label'])) l += h - #time.sleep(0.1) + time.sleep(0.1) write(result(res))