Work with specifications partly added
This commit is contained in:
parent
42d7aa9573
commit
d859399249
36
forms.py
36
forms.py
@ -4,11 +4,12 @@ import wx
|
|||||||
import wx.gizmos
|
import wx.gizmos
|
||||||
import wx.propgrid as wxpg
|
import wx.propgrid as wxpg
|
||||||
|
|
||||||
ID_TEST = wx.NewId()
|
ID_TEST = wx.NewId()
|
||||||
ID_DUPLICATE = wx.NewId()
|
ID_ADD_MODEL_ROOT = wx.NewId()
|
||||||
ID_DUPLICATE_MODEL = wx.NewId()
|
ID_ADD_MODEL_SELECTED = wx.NewId()
|
||||||
ID_DELETE_MODEL = wx.NewId()
|
ID_DUPLICATE_MODEL = wx.NewId()
|
||||||
ID_PROCESS_MODEL = wx.NewId()
|
ID_DELETE_MODEL = wx.NewId()
|
||||||
|
ID_PROCESS_MODEL = wx.NewId()
|
||||||
|
|
||||||
class MyTreeListCtrl(wx.gizmos.TreeListCtrl):
|
class MyTreeListCtrl(wx.gizmos.TreeListCtrl):
|
||||||
def Refresh(self, erase, rect):
|
def Refresh(self, erase, rect):
|
||||||
@ -23,7 +24,7 @@ class MainFrame (wx.Frame):
|
|||||||
|
|
||||||
bSizer3 = wx.BoxSizer(wx.HORIZONTAL)
|
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))
|
self.m_specs.SetMinSize(wx.Size(150,-1))
|
||||||
|
|
||||||
bSizer3.Add(self.m_specs, 0, wx.ALL|wx.EXPAND, 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 = wx.gizmos.TreeListCtrl(self,
|
||||||
#self.m_user_models = MyTreeListCtrl(self,
|
#self.m_user_models = MyTreeListCtrl(self,
|
||||||
#self.m_user_models = wx.TreeCtrl(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.SetMinSize(wx.Size(-1, 200))
|
||||||
self.m_user_models.AddColumn("Model name")
|
self.m_user_models.AddColumn("Model name")
|
||||||
self.m_user_models.AddColumn("Status")
|
self.m_user_models.AddColumn("Status")
|
||||||
self.m_user_models.AddColumn("Progress")
|
self.m_user_models.AddColumn("Progress")
|
||||||
|
self.m_user_models.AddColumn("Comment")
|
||||||
|
|
||||||
bSizer4.Add(self.m_user_models, 0, wx.ALL|wx.EXPAND, 1)
|
bSizer4.Add(self.m_user_models, 0, wx.ALL|wx.EXPAND, 1)
|
||||||
|
|
||||||
@ -59,6 +62,7 @@ class MainFrame (wx.Frame):
|
|||||||
|
|
||||||
mbar = self.BuildMenu()
|
mbar = self.BuildMenu()
|
||||||
self.SetMenuBar(mbar)
|
self.SetMenuBar(mbar)
|
||||||
|
self.BuildContextMenu()
|
||||||
|
|
||||||
self.SetSizer(bSizer3)
|
self.SetSizer(bSizer3)
|
||||||
self.Layout()
|
self.Layout()
|
||||||
@ -76,8 +80,14 @@ class MainFrame (wx.Frame):
|
|||||||
menubar.Append(menu, '&File')
|
menubar.Append(menu, '&File')
|
||||||
|
|
||||||
menu = wx.Menu()
|
menu = wx.Menu()
|
||||||
menu.Append(ID_TEST, "&Test\tCtrl+U")
|
menu.Append(ID_TEST, "&Test\tCtrl+T")
|
||||||
menu.Append(ID_DUPLICATE, "&Duplicate\tCtrl+D")
|
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')
|
menubar.Append(menu, '&Model')
|
||||||
|
|
||||||
menu = wx.Menu()
|
menu = wx.Menu()
|
||||||
@ -86,3 +96,11 @@ class MainFrame (wx.Frame):
|
|||||||
menubar.Append(menu, '&Help')
|
menubar.Append(menu, '&Help')
|
||||||
|
|
||||||
return menubar
|
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))
|
||||||
|
172
opal.py
172
opal.py
@ -20,6 +20,7 @@ import time
|
|||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
import re
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
# Главная форма
|
# Главная форма
|
||||||
@ -29,6 +30,8 @@ class MainFrame(forms.MainFrame):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
forms.MainFrame.__init__(self, None)
|
forms.MainFrame.__init__(self, None)
|
||||||
|
|
||||||
|
self.name_id = 1
|
||||||
|
|
||||||
s = server.LocalServer()
|
s = server.LocalServer()
|
||||||
s.LoadModels()
|
s.LoadModels()
|
||||||
models = s.GetModels()
|
models = s.GetModels()
|
||||||
@ -45,7 +48,14 @@ class MainFrame(forms.MainFrame):
|
|||||||
self.OnParamChanged)
|
self.OnParamChanged)
|
||||||
|
|
||||||
self.Bind(wx.EVT_MENU, self.OnTest, id = forms.ID_TEST)
|
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_CLOSE, self.OnClose)
|
||||||
self.Bind(wx.EVT_IDLE, self.OnIdle)
|
self.Bind(wx.EVT_IDLE, self.OnIdle)
|
||||||
@ -62,45 +72,127 @@ class MainFrame(forms.MainFrame):
|
|||||||
self.Destroy()
|
self.Destroy()
|
||||||
|
|
||||||
def Overseer(self):
|
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:
|
try:
|
||||||
um = self.m_user_models
|
um = self.m_user_models
|
||||||
cycle_count = 0
|
cycle_count = 0
|
||||||
while True:
|
while True:
|
||||||
wx.MutexGuiEnter()
|
wx.MutexGuiEnter()
|
||||||
print 'cycle {:-8} '.format(cycle_count)
|
print 'cycle{:-8}'.format(cycle_count)
|
||||||
cycle_count += 1
|
cycle_count += 1
|
||||||
item = um.GetRootItem()
|
item = um.GetRootItem()
|
||||||
while item.IsOk():
|
while item.IsOk():
|
||||||
data = um.GetPyData(item)
|
data = um.GetPyData(item)
|
||||||
if data:
|
if data:
|
||||||
jid = data[1]
|
jid = data[1]
|
||||||
|
|
||||||
if jid != None and self.server.IsJobChanged(jid):
|
if jid != None and self.server.IsJobChanged(jid):
|
||||||
tid = self.server.GetJobTID(jid)
|
state = self.server.GetJobState(jid)
|
||||||
meta = self.server.GetTaskMeta(tid)
|
um.SetItemText(item, StateToStr(state[0]), 1)
|
||||||
t = os.path.basename(meta['exec'])
|
p = state[1]
|
||||||
state = self.server.GetJobState(jid)
|
p = 'Unknown' if p < 0 else '{:%}'.format(p)
|
||||||
um.SetItemText(item, str(state[0]), 1)
|
um.SetItemText(item, p, 2)
|
||||||
um.SetItemText(item, '{}: {:%}'.format(t, state[1]), 2)
|
um.SetItemText(item, state[2], 3)
|
||||||
print jid, state
|
print jid, state
|
||||||
|
|
||||||
item = um.GetNext(item)
|
item = um.GetNext(item)
|
||||||
wx.MutexGuiLeave()
|
wx.MutexGuiLeave()
|
||||||
time.sleep(0.1)
|
time.sleep(0.2)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print 'Error in overseer: ', 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. загрузить спецификации модели
|
# 1. загрузить спецификации модели
|
||||||
# 2. создать одну модель по умолчанию
|
# 2. создать одну модель по умолчанию
|
||||||
model = project
|
|
||||||
um = self.m_user_models
|
|
||||||
root = um.AddRoot('Root')
|
|
||||||
data = task.DataDefinition(model)
|
|
||||||
|
|
||||||
child = um.AppendItem(root, 'Default')
|
self.BuildSpecs(model)
|
||||||
jid = self.server.CreateJob()
|
|
||||||
um.SetPyData(child, [data, jid])
|
um = self.m_user_models
|
||||||
|
um.DeleteAllItems()
|
||||||
|
um.AddRoot('Root')
|
||||||
|
|
||||||
|
self.AddModelToRoot(model)
|
||||||
|
|
||||||
|
return True # Project(model)
|
||||||
|
|
||||||
def SelectUserModel(self, model_def, jid):
|
def SelectUserModel(self, model_def, jid):
|
||||||
|
|
||||||
@ -165,12 +257,41 @@ class MainFrame(forms.MainFrame):
|
|||||||
data[param] = value
|
data[param] = value
|
||||||
|
|
||||||
def OnTest(self, event):
|
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
|
um = self.m_user_models
|
||||||
id = um.GetSelection()
|
item = um.GetSelection()
|
||||||
data, jid = um.GetItemPyData(id)
|
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):
|
def OnDuplicate(self, event):
|
||||||
um = self.m_user_models
|
um = self.m_user_models
|
||||||
@ -178,11 +299,18 @@ class MainFrame(forms.MainFrame):
|
|||||||
title = um.GetItemText(id)
|
title = um.GetItemText(id)
|
||||||
parent = um.GetItemParent(id)
|
parent = um.GetItemParent(id)
|
||||||
md, jid = um.GetItemPyData(id)
|
md, jid = um.GetItemPyData(id)
|
||||||
child = um.AppendItem(parent, title + ' Copy')
|
|
||||||
|
child = um.AppendItem(parent, self.GenerateName(title))
|
||||||
jid = self.server.CreateJob()
|
jid = self.server.CreateJob()
|
||||||
um.SetPyData(child, [md.Copy(), jid])
|
um.SetPyData(child, [md.Copy(), jid])
|
||||||
self.SetStatusText('Copy for "{}" created'.format(title), 0)
|
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):
|
def OnIdle(self, event):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -30,6 +30,9 @@ def WriteToLog(msg):
|
|||||||
#self.log.write(msg + '\n')
|
#self.log.write(msg + '\n')
|
||||||
print msg
|
print msg
|
||||||
|
|
||||||
|
class JIDError(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Invalid jid'
|
||||||
|
|
||||||
class LocalServer:
|
class LocalServer:
|
||||||
def __init__(self, conf = 'tasks.conf', workers = 2):
|
def __init__(self, conf = 'tasks.conf', workers = 2):
|
||||||
@ -127,6 +130,9 @@ class LocalServer:
|
|||||||
def GetJobsCount(self):
|
def GetJobsCount(self):
|
||||||
return len(self.jobs)
|
return len(self.jobs)
|
||||||
|
|
||||||
|
#def CheckJID(self, func):
|
||||||
|
# def
|
||||||
|
|
||||||
def GetJobState(self, jid):
|
def GetJobState(self, jid):
|
||||||
job = self.jobs.get(jid)
|
job = self.jobs.get(jid)
|
||||||
if job != None:
|
if job != None:
|
||||||
@ -302,6 +308,7 @@ class Job:
|
|||||||
self.tid = tid
|
self.tid = tid
|
||||||
self.datadump = datadump
|
self.datadump = datadump
|
||||||
self.state = JOB_READY
|
self.state = JOB_READY
|
||||||
|
self.percent = -1.0
|
||||||
self.ChangeState()
|
self.ChangeState()
|
||||||
|
|
||||||
def Stop(self):
|
def Stop(self):
|
||||||
|
5
task.py
5
task.py
@ -76,7 +76,10 @@ class DataDescription:
|
|||||||
def GetId(self):
|
def GetId(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def GetSpecifications(self):
|
def GetParent(self):
|
||||||
|
return self.parent
|
||||||
|
|
||||||
|
def GetSpecs(self):
|
||||||
return self.specs
|
return self.specs
|
||||||
|
|
||||||
def IsExecutable(self):
|
def IsExecutable(self):
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"title": "Example task",
|
"title": "Example task",
|
||||||
"author": "Anton Vakhrushev",
|
"author": "Anton Vakhrushev",
|
||||||
"meta": "av-example-task",
|
"meta": "av-example-task-version-00-10",
|
||||||
|
|
||||||
"models": {
|
"models": {
|
||||||
|
|
||||||
"sintaylor": {
|
"sintaylor": {
|
||||||
|
|
||||||
"title": "Simple model for example",
|
"title": "Simple model",
|
||||||
"author": "Anton Vakhrushev",
|
"author": "Anton Vakhrushev",
|
||||||
"date": "2012-03-08",
|
"date": "2012-03-08",
|
||||||
|
|
||||||
@ -31,10 +31,25 @@
|
|||||||
|
|
||||||
"n": {
|
"n": {
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"default": 10,
|
"default": 100,
|
||||||
"title": "Steps",
|
"title": "Steps",
|
||||||
"comment": "Number of steps for algorithm"
|
"comment": "Number of steps for algorithm"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"spec": {
|
||||||
|
|
||||||
|
"left": {
|
||||||
|
"title": "Left"
|
||||||
|
},
|
||||||
|
|
||||||
|
"right": {
|
||||||
|
"title": "Right"
|
||||||
|
},
|
||||||
|
|
||||||
|
"trapezium": {
|
||||||
|
"title": "Trapezium"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,11 @@ def sin_taylor(x, n):
|
|||||||
e *= -1
|
e *= -1
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def answer(p):
|
def answer(p, c = ''):
|
||||||
return json.dumps({
|
return json.dumps({
|
||||||
"answer": "ok",
|
"answer": "ok",
|
||||||
"value": p
|
"value": p,
|
||||||
|
"comment": c
|
||||||
})
|
})
|
||||||
|
|
||||||
def error(msg):
|
def error(msg):
|
||||||
@ -61,7 +62,7 @@ def main():
|
|||||||
textdata = raw_input()
|
textdata = raw_input()
|
||||||
data = json.loads(textdata)
|
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'))
|
write(error('Unknown model'))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -76,9 +77,9 @@ def main():
|
|||||||
while l <= r:
|
while l <= r:
|
||||||
y = sin_taylor(l, d)
|
y = sin_taylor(l, d)
|
||||||
res.append([l, y])
|
res.append([l, y])
|
||||||
write(answer(l / r))
|
write(answer(l / r, data[-1]['label']))
|
||||||
l += h
|
l += h
|
||||||
#time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
write(result(res))
|
write(result(res))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user