2015-05-22 17:05:15 +08:00
|
|
|
import asyncio
|
|
|
|
import time
|
2015-10-06 13:50:00 +08:00
|
|
|
from functools import partial
|
2016-05-29 00:04:15 +08:00
|
|
|
import logging
|
2015-05-22 17:05:15 +08:00
|
|
|
|
2016-03-25 18:33:22 +08:00
|
|
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
2015-05-22 17:05:15 +08:00
|
|
|
|
2015-11-11 12:13:19 +08:00
|
|
|
from artiq.gui.models import DictSyncModel
|
2015-10-12 19:46:14 +08:00
|
|
|
from artiq.tools import elide
|
2015-05-22 17:05:15 +08:00
|
|
|
|
|
|
|
|
2016-05-29 00:04:15 +08:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2015-11-11 12:13:19 +08:00
|
|
|
class Model(DictSyncModel):
|
|
|
|
def __init__(self, init):
|
2015-05-22 17:05:15 +08:00
|
|
|
DictSyncModel.__init__(self,
|
2015-05-24 01:09:22 +08:00
|
|
|
["RID", "Pipeline", "Status", "Prio", "Due date",
|
2015-08-07 15:51:56 +08:00
|
|
|
"Revision", "File", "Class name"],
|
2015-11-11 12:13:19 +08:00
|
|
|
init)
|
2015-05-22 17:05:15 +08:00
|
|
|
|
|
|
|
def sort_key(self, k, v):
|
2015-05-28 18:24:26 +08:00
|
|
|
# order by priority, and then by due date and RID
|
|
|
|
return (-v["priority"], v["due_date"] or 0, k)
|
2015-05-22 17:05:15 +08:00
|
|
|
|
|
|
|
def convert(self, k, v, column):
|
|
|
|
if column == 0:
|
|
|
|
return k
|
|
|
|
elif column == 1:
|
|
|
|
return v["pipeline"]
|
|
|
|
elif column == 2:
|
|
|
|
return v["status"]
|
|
|
|
elif column == 3:
|
2015-05-24 01:09:22 +08:00
|
|
|
return str(v["priority"])
|
|
|
|
elif column == 4:
|
2015-05-22 17:05:15 +08:00
|
|
|
if v["due_date"] is None:
|
|
|
|
return ""
|
|
|
|
else:
|
|
|
|
return time.strftime("%m/%d %H:%M:%S",
|
|
|
|
time.localtime(v["due_date"]))
|
|
|
|
elif column == 5:
|
2015-08-07 15:51:56 +08:00
|
|
|
expid = v["expid"]
|
|
|
|
if "repo_rev" in expid:
|
2015-08-08 11:08:04 +08:00
|
|
|
r = expid["repo_rev"]
|
|
|
|
if v["repo_msg"]:
|
|
|
|
r += "\n" + elide(v["repo_msg"], 40)
|
|
|
|
return r
|
2015-08-07 15:51:56 +08:00
|
|
|
else:
|
|
|
|
return "Outside repo."
|
2015-05-24 01:09:22 +08:00
|
|
|
elif column == 6:
|
2022-03-20 12:58:55 +08:00
|
|
|
return v["expid"].get("file", "<none>")
|
2015-08-07 15:51:56 +08:00
|
|
|
elif column == 7:
|
2015-07-15 17:08:12 +08:00
|
|
|
if v["expid"]["class_name"] is None:
|
2015-05-22 17:05:15 +08:00
|
|
|
return ""
|
|
|
|
else:
|
2015-07-15 17:08:12 +08:00
|
|
|
return v["expid"]["class_name"]
|
2015-05-22 17:05:15 +08:00
|
|
|
else:
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
|
2016-02-14 19:15:57 +08:00
|
|
|
class ScheduleDock(QtWidgets.QDockWidget):
|
2016-05-29 00:04:15 +08:00
|
|
|
def __init__(self, schedule_ctl, schedule_sub):
|
2016-02-14 19:15:57 +08:00
|
|
|
QtWidgets.QDockWidget.__init__(self, "Schedule")
|
2016-02-14 20:46:15 +08:00
|
|
|
self.setObjectName("Schedule")
|
2016-02-14 19:15:57 +08:00
|
|
|
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
|
|
|
QtWidgets.QDockWidget.DockWidgetFloatable)
|
2015-05-22 17:05:15 +08:00
|
|
|
|
2015-05-24 23:20:52 +08:00
|
|
|
self.schedule_ctl = schedule_ctl
|
|
|
|
|
2016-02-15 07:23:47 +08:00
|
|
|
self.table = QtWidgets.QTableView()
|
|
|
|
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
|
|
|
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
|
|
|
self.table.verticalHeader().setSectionResizeMode(
|
|
|
|
QtWidgets.QHeaderView.ResizeToContents)
|
2016-01-07 16:56:48 +08:00
|
|
|
self.table.verticalHeader().hide()
|
2016-02-14 19:15:57 +08:00
|
|
|
self.setWidget(self.table)
|
2015-05-22 17:05:15 +08:00
|
|
|
|
2015-05-24 23:20:52 +08:00
|
|
|
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
2016-02-15 07:23:47 +08:00
|
|
|
request_termination_action = QtWidgets.QAction("Request termination", self.table)
|
2015-10-06 13:50:00 +08:00
|
|
|
request_termination_action.triggered.connect(partial(self.delete_clicked, True))
|
2015-10-27 18:20:25 +08:00
|
|
|
request_termination_action.setShortcut("DELETE")
|
2016-01-17 03:53:53 +08:00
|
|
|
request_termination_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
|
2015-10-06 13:50:00 +08:00
|
|
|
self.table.addAction(request_termination_action)
|
2016-02-15 07:23:47 +08:00
|
|
|
delete_action = QtWidgets.QAction("Delete", self.table)
|
2015-10-06 13:50:00 +08:00
|
|
|
delete_action.triggered.connect(partial(self.delete_clicked, False))
|
2015-10-27 18:20:25 +08:00
|
|
|
delete_action.setShortcut("SHIFT+DELETE")
|
2016-01-17 03:53:53 +08:00
|
|
|
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
|
2015-05-24 23:20:52 +08:00
|
|
|
self.table.addAction(delete_action)
|
2016-03-29 17:34:38 +08:00
|
|
|
terminate_pipeline = QtWidgets.QAction(
|
|
|
|
"Gracefully terminate all in pipeline", self.table)
|
|
|
|
terminate_pipeline.triggered.connect(self.terminate_pipeline_clicked)
|
|
|
|
self.table.addAction(terminate_pipeline)
|
2015-05-24 23:20:52 +08:00
|
|
|
|
2015-11-11 12:13:19 +08:00
|
|
|
self.table_model = Model(dict())
|
|
|
|
schedule_sub.add_setmodel_callback(self.set_model)
|
2015-05-22 17:05:15 +08:00
|
|
|
|
2016-03-25 18:33:22 +08:00
|
|
|
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
|
|
|
h = self.table.horizontalHeader()
|
|
|
|
h.resizeSection(0, 7*cw)
|
|
|
|
h.resizeSection(1, 12*cw)
|
|
|
|
h.resizeSection(2, 16*cw)
|
|
|
|
h.resizeSection(3, 6*cw)
|
|
|
|
h.resizeSection(4, 16*cw)
|
|
|
|
h.resizeSection(5, 30*cw)
|
|
|
|
h.resizeSection(6, 20*cw)
|
|
|
|
h.resizeSection(7, 20*cw)
|
2016-03-17 13:20:29 +08:00
|
|
|
|
2015-11-11 12:13:19 +08:00
|
|
|
def set_model(self, model):
|
|
|
|
self.table_model = model
|
2015-05-24 23:20:52 +08:00
|
|
|
self.table.setModel(self.table_model)
|
|
|
|
|
2015-10-06 13:50:00 +08:00
|
|
|
async def delete(self, rid, graceful):
|
|
|
|
if graceful:
|
|
|
|
await self.schedule_ctl.request_termination(rid)
|
|
|
|
else:
|
|
|
|
await self.schedule_ctl.delete(rid)
|
2015-05-24 23:20:52 +08:00
|
|
|
|
2015-10-06 13:50:00 +08:00
|
|
|
def delete_clicked(self, graceful):
|
2015-05-24 23:20:52 +08:00
|
|
|
idx = self.table.selectedIndexes()
|
|
|
|
if idx:
|
|
|
|
row = idx[0].row()
|
|
|
|
rid = self.table_model.row_to_key[row]
|
2015-10-27 18:20:11 +08:00
|
|
|
if graceful:
|
2016-05-29 00:04:15 +08:00
|
|
|
logger.info("Requested termination of RID %d", rid)
|
2015-10-27 18:20:11 +08:00
|
|
|
else:
|
2016-05-29 00:04:15 +08:00
|
|
|
logger.info("Deleted RID %d", rid)
|
2015-10-06 13:50:00 +08:00
|
|
|
asyncio.ensure_future(self.delete(rid, graceful))
|
2016-03-25 18:33:22 +08:00
|
|
|
|
2016-03-29 17:34:38 +08:00
|
|
|
async def request_term_multiple(self, rids):
|
|
|
|
for rid in rids:
|
|
|
|
try:
|
|
|
|
await self.schedule_ctl.request_termination(rid)
|
|
|
|
except:
|
|
|
|
# May happen if the experiment has terminated by itself
|
|
|
|
# while we were terminating others.
|
|
|
|
logger.debug("failed to request termination of RID %d",
|
|
|
|
rid, exc_info=True)
|
|
|
|
|
|
|
|
def terminate_pipeline_clicked(self):
|
|
|
|
idx = self.table.selectedIndexes()
|
|
|
|
if idx:
|
|
|
|
row = idx[0].row()
|
|
|
|
selected_rid = self.table_model.row_to_key[row]
|
|
|
|
pipeline = self.table_model.backing_store[selected_rid]["pipeline"]
|
2016-07-19 00:01:59 +08:00
|
|
|
logger.info("Requesting termination of all "
|
|
|
|
"experiments in pipeline '%s'", pipeline)
|
2016-03-29 17:34:38 +08:00
|
|
|
|
|
|
|
rids = set()
|
|
|
|
for rid, info in self.table_model.backing_store.items():
|
|
|
|
if info["pipeline"] == pipeline:
|
|
|
|
rids.add(rid)
|
|
|
|
asyncio.ensure_future(self.request_term_multiple(rids))
|
|
|
|
|
|
|
|
|
2016-03-25 18:33:22 +08:00
|
|
|
def save_state(self):
|
|
|
|
return bytes(self.table.horizontalHeader().saveState())
|
|
|
|
|
|
|
|
def restore_state(self, state):
|
|
|
|
self.table.horizontalHeader().restoreState(QtCore.QByteArray(state))
|