diff --git a/artiq/frontend/artiq_client.py b/artiq/frontend/artiq_client.py
index d4ce1926c..4259fc1c5 100755
--- a/artiq/frontend/artiq_client.py
+++ b/artiq/frontend/artiq_client.py
@@ -98,7 +98,7 @@ def get_argparser():
parser_ls = subparsers.add_parser(
"ls", help="list a directory on the master")
- parser_ls.add_argument("directory")
+ parser_ls.add_argument("directory", default="", nargs="?")
return parser
@@ -160,11 +160,8 @@ def _action_scan_repository(remote, args):
def _action_ls(remote, args):
contents = remote.list_directory(args.directory)
- for name, is_dir in sorted(contents, key=lambda x: (-x[1], x[0])):
- if is_dir:
- print("
" + name)
- else:
- print(" " + name)
+ for name in sorted(contents, key=lambda x: (x[-1] not in "\\/", x)):
+ print(name)
def _show_schedule(schedule):
diff --git a/artiq/gui/explorer.py b/artiq/gui/explorer.py
index 777811a65..0eb782f06 100644
--- a/artiq/gui/explorer.py
+++ b/artiq/gui/explorer.py
@@ -13,36 +13,102 @@ logger = logging.getLogger(__name__)
class _OpenFileDialog(QtGui.QDialog):
- def __init__(self, parent, exp_manager):
- QtGui.QDialog.__init__(self, parent=parent)
+ def __init__(self, explorer, exp_manager, experiment_db_ctl):
+ QtGui.QDialog.__init__(self, parent=explorer)
+ self.resize(710, 700)
self.setWindowTitle("Open file outside repository")
+ self.explorer = explorer
self.exp_manager = exp_manager
+ self.experiment_db_ctl = experiment_db_ctl
grid = QtGui.QGridLayout()
self.setLayout(grid)
- grid.addWidget(QtGui.QLabel("Filename:"), 0, 0)
- self.filename = QtGui.QLineEdit()
- grid.addWidget(self.filename, 0, 1)
+ grid.addWidget(QtGui.QLabel("Location:"), 0, 0)
+ self.location_label = QtGui.QLabel("")
+ grid.addWidget(self.location_label, 0, 1)
+ grid.setColumnStretch(1, 1)
+
+ self.file_list = QtGui.QListWidget()
+ asyncio.ensure_future(self.refresh_view())
+ grid.addWidget(self.file_list, 1, 0, 1, 2)
+ self.file_list.doubleClicked.connect(self.accept)
buttons = QtGui.QDialogButtonBox(
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
- grid.addWidget(buttons, 1, 0, 1, 2)
+ grid.addWidget(buttons, 2, 0, 1, 2)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
- self.accepted.connect(self.open_file)
+ async def refresh_view(self):
+ self.file_list.clear()
+ if not self.explorer.current_directory:
+ self.location_label.setText("")
+ else:
+ self.location_label.setText(self.explorer.current_directory)
- def open_file(self):
- file = self.filename.text()
- async def open_task():
- try:
- await self.exp_manager.open_file(file)
- except:
- logger.error("Failed to open file '%s'",
- file, exc_info=True)
- asyncio.ensure_future(open_task())
+ item = QtGui.QListWidgetItem()
+ item.setText("..")
+ item.setIcon(QtGui.QApplication.style().standardIcon(
+ QtGui.QStyle.SP_FileDialogToParent))
+ self.file_list.addItem(item)
+
+ try:
+ contents = await self.experiment_db_ctl.list_directory(
+ self.explorer.current_directory)
+ except:
+ logger.error("Failed to list directory '%s'",
+ self.explorer.current_directory, exc_info=True)
+ self.explorer.current_directory = ""
+ for name in sorted(contents, key=lambda x: (x[-1] not in "\\/", x)):
+ if name[-1] in "\\/":
+ icon = QtGui.QStyle.SP_DirIcon
+ else:
+ icon = QtGui.QStyle.SP_FileIcon
+ if name[-3:] != ".py":
+ continue
+ item = QtGui.QListWidgetItem()
+ item.setText(name)
+ item.setIcon(QtGui.QApplication.style().standardIcon(icon))
+ self.file_list.addItem(item)
+
+ def accept(self):
+ selected = self.file_list.selectedItems()
+ if selected:
+ selected = selected[0].text()
+ if selected == "..":
+ if (not self.explorer.current_directory
+ or self.explorer.current_directory[-1] not in "\\/"):
+ return
+ idx = None
+ for sep in "\\/":
+ try:
+ idx = self.explorer.current_directory[:-1].rindex(sep)
+ except ValueError:
+ pass
+ else:
+ break
+ if idx is None:
+ return
+ self.explorer.current_directory = \
+ self.explorer.current_directory[:idx+1]
+ if self.explorer.current_directory == "/":
+ self.explorer.current_directory = ""
+ asyncio.ensure_future(self.refresh_view())
+ elif selected[-1] in "\\/":
+ self.explorer.current_directory += selected
+ asyncio.ensure_future(self.refresh_view())
+ else:
+ file = self.explorer.current_directory + selected
+ async def open_task():
+ try:
+ await self.exp_manager.open_file(file)
+ except:
+ logger.error("Failed to open file '%s'",
+ file, exc_info=True)
+ asyncio.ensure_future(open_task())
+ QtGui.QDialog.accept(self)
class Model(DictSyncTreeSepModel):
@@ -127,10 +193,12 @@ class ExplorerDock(dockarea.Dock):
scan_repository_action.triggered.connect(scan_repository)
self.el.addAction(scan_repository_action)
+ self.current_directory = ""
open_file_action = QtGui.QAction("Open file outside repository",
self.el)
open_file_action.triggered.connect(
- lambda: _OpenFileDialog(self, self.exp_manager).open())
+ lambda: _OpenFileDialog(self, self.exp_manager,
+ experiment_db_ctl).open())
self.el.addAction(open_file_action)
def set_model(self, model):
diff --git a/artiq/master/experiments.py b/artiq/master/experiments.py
index b6ea84fca..e1fe67ee8 100644
--- a/artiq/master/experiments.py
+++ b/artiq/master/experiments.py
@@ -7,7 +7,7 @@ from functools import partial
from artiq.protocols.sync_struct import Notifier
from artiq.master.worker import Worker
-from artiq.tools import exc_to_warning
+from artiq.tools import get_windows_drives, exc_to_warning
logger = logging.getLogger(__name__)
@@ -130,7 +130,21 @@ class ExperimentDB:
return description
def list_directory(self, directory):
- return [(de.name, de.is_dir()) for de in os.scandir(directory)]
+ r = []
+ prefix = ""
+ if not directory:
+ if os.name == "nt":
+ drives = get_windows_drives()
+ return [drive + ":\\" for drive in drives]
+ else:
+ directory = "/"
+ prefix = "/"
+ for de in os.scandir(directory):
+ if de.is_dir():
+ r.append(prefix + de.name + os.path.sep)
+ else:
+ r.append(prefix + de.name)
+ return r
class FilesystemBackend:
diff --git a/artiq/tools.py b/artiq/tools.py
index b50cc6fd3..f1167ab39 100644
--- a/artiq/tools.py
+++ b/artiq/tools.py
@@ -8,6 +8,7 @@ import time
import collections
import os
import atexit
+import string
import numpy as np
@@ -18,7 +19,7 @@ from artiq.protocols import pyon
__all__ = ["artiq_dir", "parse_arguments", "elide", "short_format", "file_import",
"get_experiment", "verbosity_args", "simple_network_args", "init_logger",
"atexit_register_coroutine", "exc_to_warning", "asyncio_wait_or_cancel",
- "TaskObject", "Condition", "workaround_asyncio263"]
+ "TaskObject", "Condition", "workaround_asyncio263", "get_windows_drives"]
logger = logging.getLogger(__name__)
@@ -198,3 +199,15 @@ class Condition:
@asyncio.coroutine
def workaround_asyncio263():
yield
+
+
+def get_windows_drives():
+ from ctypes import windll
+
+ drives = []
+ bitmask = windll.kernel32.GetLogicalDrives()
+ for letter in string.ascii_uppercase:
+ if bitmask & 1:
+ drives.append(letter)
+ bitmask >>= 1
+ return drives