# Module `redvox.common.gui.cloud_data_retrieval`

Expand source code

    try:
        import datetime
        from multiprocessing import Manager, Process, Queue
        import os.path
        import sys
        from typing import List, Optional

        # noinspection Mypy
        from redvox.cloud.client import cloud_client, CloudClient

        # noinspection Mypy
        from redvox.cloud.config import RedVoxConfig

        # noinspection Mypy
        from redvox.cloud.data_api import DataRangeReqType

        # noinspection PyPackageRequirements
        import PySide6.QtCore

        # noinspection PyPackageRequirements
        from PySide6.QtGui import QIntValidator

        # noinspection PyPackageRequirements
        from PySide6 import QtWidgets

        # noinspection PyPackageRequirements
        from PySide6.QtWidgets import (
            QApplication,
            QCheckBox,
            QDateTimeEdit,
            QDialog,
            QFileDialog,
            QHBoxLayout,
            QLabel,
            QLineEdit,
            QPushButton,
            QRadioButton,
            QTextEdit,
            QVBoxLayout,
            QWidget,
        )

        class WindowSelectionWidget(QWidget):
            def __init__(self, parent=None):
                super(WindowSelectionWidget, self).__init__(parent)

                self.start_dt: QDateTimeEdit = QDateTimeEdit()
                self.end_dt: QDateTimeEdit = QDateTimeEdit()

                now: datetime.datetime = datetime.datetime.now()
                fifteen_minutes_ago: datetime.datetime = now - datetime.timedelta(
                    minutes=15
                )
                date_fmt: str = "yyyy-MM-dd HH:mm UTC"

                self.start_dt.setDisplayFormat(date_fmt)
                self.start_dt.setTimeSpec(PySide6.QtCore.Qt.UTC)
                self.end_dt.setDisplayFormat(date_fmt)
                self.end_dt.setTimeSpec(PySide6.QtCore.Qt.UTC)
                self.start_dt.setCalendarPopup(True)
                self.end_dt.setCalendarPopup(True)
                self.start_dt.setDateTime(fifteen_minutes_ago)
                self.end_dt.setDateTime(now)

                layout: QHBoxLayout = QHBoxLayout()

                layout.addWidget(QLabel("Start"))
                layout.addWidget(self.start_dt)
                layout.addWidget(QLabel("End"))
                layout.addWidget(self.end_dt)

                self.setLayout(layout)

            def validate(self) -> List[str]:
                errors: List[str] = []
                if self.start_dt.dateTime() >= self.end_dt.dateTime():
                    errors.append(
                        "Start of time window must be strictly less than end of time window"
                    )
                return errors

        def empty_text(s: str) -> bool:
            return s.strip() == ""

        class ServerSelectionWidget(QWidget):
            def __init__(self, redvox_config: Optional[RedVoxConfig], parent=None):
                super(ServerSelectionWidget, self).__init__(parent)

                int_validator = QIntValidator()

                self.https: QRadioButton = QRadioButton()
                self.https.setText("HTTPS")
                self.http: QRadioButton = QRadioButton()
                self.http.setText("HTTP")

                self.host: QLineEdit = QLineEdit()
                self.port: QLineEdit = QLineEdit()
                self.port.setValidator(int_validator)

                self.timeout: QLineEdit = QLineEdit()
                self.retries: QLineEdit = QLineEdit()

                self.timeout.setValidator(int_validator)
                self.retries.setValidator(int_validator)

                self.disable_timing_correction: QCheckBox = QCheckBox()

                if redvox_config is not None:
                    self.host.setText(redvox_config.host)
                    self.port.setText(str(redvox_config.port))
                    if redvox_config.protocol == "http":
                        self.http.setChecked(True)
                    else:
                        self.https.setChecked(True)
                else:
                    self.https.setChecked(True)
                    self.host.setText("redvox.io")
                    self.port.setText("8080")

                self.timeout.setText("120")
                self.retries.setText("3")

                self.disable_timing_correction.setText("Disable Timing Correction")
                self.disable_timing_correction.setChecked(False)

                layout: QVBoxLayout = QVBoxLayout()
                server_widget: QWidget = QWidget()
                server_options_widget: QWidget = QWidget()
                server_layout: QHBoxLayout = QHBoxLayout()
                server_options_layout: QHBoxLayout = QHBoxLayout()
                server_widget.setLayout(server_layout)
                server_options_widget.setLayout(server_options_layout)

                server_layout.addWidget(QLabel("Protocol"))
                server_layout.addWidget(self.https)
                server_layout.addWidget(self.http)
                server_layout.addWidget(QLabel("Host"))
                server_layout.addWidget(self.host)
                server_layout.addWidget(QLabel("Port"))
                server_layout.addWidget(self.port)

                server_options_layout.addWidget(QLabel("Timeout"))
                server_options_layout.addWidget(self.timeout)
                server_options_layout.addWidget(QLabel("Retries"))
                server_options_layout.addWidget(self.retries)
                server_options_layout.addWidget(self.disable_timing_correction)

                layout.addWidget(server_widget)
                layout.addWidget(server_options_widget)

                self.setLayout(layout)

            def validate(self) -> List[str]:
                errors: List[str] = []
                if empty_text(self.host.text()):
                    errors.append("Host must be provided")
                if empty_text(self.port.text()):
                    errors.append("Port must be provided")
                if empty_text(self.timeout.text()):
                    errors.append("Timeout must be provided")
                if empty_text(self.retries.text()):
                    errors.append("Retries must be provided")
                return errors

        class AuthenticationWidget(QWidget):
            def __init__(self, redvox_config: Optional[RedVoxConfig], parent=None):
                super(AuthenticationWidget, self).__init__(parent)

                self.username: QLineEdit = QLineEdit()
                self.password: QLineEdit = QLineEdit()
                self.password.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)

                if redvox_config is not None:
                    self.username.setText(redvox_config.username)
                    self.password.setText(redvox_config.password)

                layout: QHBoxLayout = QHBoxLayout()

                layout.addWidget(QLabel("Username"))
                layout.addWidget(self.username)

                layout.addWidget(QLabel("Password"))
                layout.addWidget(self.password)

                self.setLayout(layout)

            def validate(self) -> List[str]:
                errors: List[str] = []
                if empty_text(self.username.text()):
                    errors.append("Username must be provided")
                if empty_text(self.password.text()):
                    errors.append("Password must be provided")
                return errors

        class ApiSelectionWidget(QWidget):
            def __init__(self, parent=None):
                super(ApiSelectionWidget, self).__init__(parent)

                self.api_900_1000: QRadioButton = QRadioButton()
                self.api_900_1000.setText("API 900 and 1000")
                self.api_900: QRadioButton = QRadioButton()
                self.api_900.setText("API 900")
                self.api_1000: QRadioButton = QRadioButton()
                self.api_1000.setText("API 1000")

                self.api_900_1000.setChecked(True)

                layout: QHBoxLayout = QHBoxLayout()
                layout.addWidget(self.api_900_1000)
                layout.addWidget(self.api_900)
                layout.addWidget(self.api_1000)

                self.setLayout(layout)

        class OutputDirectoryWidget(QWidget):
            def __init__(self, parent=None):
                super(OutputDirectoryWidget, self).__init__(parent)

                self.output_dir: QLineEdit = QLineEdit()
                self.open_file: QPushButton = QPushButton("Select")

                # noinspection PyUnresolvedReferences
                self.open_file.clicked.connect(self.open_dialog)

                self.output_dir.setText(".")

                layout: QHBoxLayout = QHBoxLayout()
                layout.addWidget(self.output_dir)
                layout.addWidget(self.open_file)

                self.setLayout(layout)

            def open_dialog(self):
                self.output_dir.setText(
                    QFileDialog.getExistingDirectory(options=QFileDialog.ShowDirsOnly)
                )

            def validate(self) -> List[str]:
                errors: List[str] = []

                if empty_text(self.output_dir.text()):
                    errors.append("Output directory must be set")

                if not os.path.isdir(self.output_dir.text()):
                    errors.append("Output directory does not exist")

                return errors

        def download_process(
            redvox_config: RedVoxConfig,
            timeout: int,
            req_start: int,
            req_end: int,
            station_ids: List[str],
            api_type: DataRangeReqType,
            correct_query_timing: bool,
            out_dir: str,
            retries: int,
            out_queue: Queue,
        ):

            try:
                client: CloudClient
                with cloud_client(redvox_config, timeout=float(timeout)) as client:
                    resp = client.request_data_range(
                        req_start,
                        req_end,
                        station_ids,
                        api_type,
                        correct_query_timing,
                        out_queue=out_queue,
                    )
                    resp.download_fs(out_dir, retries, out_queue=out_queue)
            except Exception as e:
                out_queue.put(f"Encountered an error: {e}")
                out_queue.put("done")

        class DataDownloadGui(QDialog):
            def __init__(self, parent=None):
                super(DataDownloadGui, self).__init__(parent)
                self.setWindowTitle("RedVox Cloud Data Downloader")
                self.setMinimumWidth(1600)
                self.setMinimumHeight(900)

                redvox_config: Optional[RedVoxConfig] = RedVoxConfig.find()

                self.authentication_widget: AuthenticationWidget = AuthenticationWidget(
                    redvox_config
                )
                self.server_selection_widget: ServerSelectionWidget = ServerSelectionWidget(
                    redvox_config
                )
                self.window_selection_widget: WindowSelectionWidget = (
                    WindowSelectionWidget()
                )
                self.station_ids_widget: QTextEdit = QTextEdit()
                self.api_selection_widget: ApiSelectionWidget = ApiSelectionWidget()
                self.output_directory_widget: OutputDirectoryWidget = (
                    OutputDirectoryWidget()
                )
                self.download_data_btn: QPushButton = QPushButton("Download Data")
                # noinspection PyUnresolvedReferences
                self.download_data_btn.clicked.connect(self.download_data)

                self.log: QTextEdit = QTextEdit()
                self.log.setReadOnly(True)
                self.info = lambda msg: self.log.append(
                    f"{datetime.datetime.utcnow()}: {msg}"
                )

                layout: QVBoxLayout = QVBoxLayout()

                layout.addWidget(QLabel("RedVox Authentication"))
                layout.addWidget(self.authentication_widget)

                layout.addWidget(QLabel("Server Selection"))
                layout.addWidget(self.server_selection_widget)

                layout.addWidget(QLabel("Data Window Selection"))
                layout.addWidget(self.window_selection_widget)

                layout.addWidget(QLabel("Station IDs (one per line)"))
                layout.addWidget(self.station_ids_widget)

                layout.addWidget(QLabel("API Selection"))
                layout.addWidget(self.api_selection_widget)

                layout.addWidget(QLabel("Output Directory"))
                layout.addWidget(self.output_directory_widget)

                layout.addWidget(self.download_data_btn)

                layout.addWidget(QLabel("Log"))
                layout.addWidget(self.log)

                self.setLayout(layout)

            def download_data(self):
                validation_errors: List[str] = self.validate()
                if len(validation_errors) > 0:
                    self.info("Errors encountered")
                    for error in validation_errors:
                        self.info(error)
                    return

                username: str = self.authentication_widget.username.text()
                password: str = self.authentication_widget.password.text()
                protocol: str
                if self.server_selection_widget.https.isChecked():
                    protocol = "https"
                else:
                    protocol = "http"
                host: str = self.server_selection_widget.host.text()
                port: int = int(self.server_selection_widget.port.text())
                out_dir: str = self.output_directory_widget.output_dir.text()
                redvox_config: RedVoxConfig = RedVoxConfig(
                    username, password, protocol, host, port
                )
                req_start: int = int(
                    self.window_selection_widget.start_dt.dateTime().toSecsSinceEpoch()
                )
                req_end: int = int(
                    self.window_selection_widget.end_dt.dateTime().toSecsSinceEpoch()
                )
                station_ids: List[str] = list(map(str.strip, self.station_ids_widget.toPlainText().splitlines(
                    False
                )))
                api_type: DataRangeReqType
                if self.api_selection_widget.api_900_1000.isChecked():
                    # noinspection PyTypeChecker
                    api_type = DataRangeReqType.API_900_1000
                elif self.api_selection_widget.api_1000.isChecked():
                    # noinspection PyTypeChecker
                    api_type = DataRangeReqType.API_1000
                else:
                    # noinspection PyTypeChecker
                    api_type = DataRangeReqType.API_900

                retries: int = int(self.server_selection_widget.retries.text())
                timeout: int = int(self.server_selection_widget.timeout.text())
                correct_query_timing: bool = (
                    not self.server_selection_widget.disable_timing_correction.isChecked()
                )

                manager = Manager()
                out_queue = manager.Queue(1024)

                self.info("Starting data query")

                process = Process(
                    target=download_process,
                    args=(
                        redvox_config,
                        timeout,
                        req_start,
                        req_end,
                        station_ids,
                        api_type,
                        correct_query_timing,
                        out_dir,
                        retries,
                        out_queue,
                    ),
                )

                process.start()

                result: str = ""
                while result != "done":
                    # noinspection PyBroadException
                    try:
                        result = out_queue.get(block=True, timeout=0.1)
                        self.info(result)
                        QApplication.processEvents()
                    except:
                        pass

            def validate(self) -> List[str]:
                errors: List[str] = []

                station_ids: List[str] = self.station_ids_widget.toPlainText().splitlines()
                if len(station_ids) == 0:
                    errors.append("At least one station ID must be provided")

                for station_id in station_ids:
                    try:
                        int(station_id)
                    except ValueError:
                        errors.append(f"{station_id} does not appear to be a valid station")

                for widget in [
                    self.window_selection_widget,
                    self.authentication_widget,
                    self.server_selection_widget,
                    self.output_directory_widget,
                ]:
                    # noinspection Mypy
                    errors.extend(widget.validate())

                return errors

        def run_gui() -> None:
            app = QApplication(sys.argv)
            form = DataDownloadGui()
            form.show()
            sys.exit(app.exec_())

        if __name__ == "__main__":
            run_gui()

    except ImportError:
        import warnings

        warnings.warn(
            "GUI dependencies are not installed. Install the 'GUI' extra to enable this functionality."
        )

## Functions

`def download_process(redvox_config: RedVoxConfig, timeout: int, req_start: int, req_end: int, station_ids: List[str], api_type: DataRangeReqType, correct_query_timing: bool, out_dir: str, retries: int, out_queue: >)`
  Expand source code

      def download_process(
          redvox_config: RedVoxConfig,
          timeout: int,
          req_start: int,
          req_end: int,
          station_ids: List[str],
          api_type: DataRangeReqType,
          correct_query_timing: bool,
          out_dir: str,
          retries: int,
          out_queue: Queue,
      ):

          try:
              client: CloudClient
              with cloud_client(redvox_config, timeout=float(timeout)) as client:
                  resp = client.request_data_range(
                      req_start,
                      req_end,
                      station_ids,
                      api_type,
                      correct_query_timing,
                      out_queue=out_queue,
                  )
                  resp.download_fs(out_dir, retries, out_queue=out_queue)
          except Exception as e:
              out_queue.put(f"Encountered an error: {e}")
              out_queue.put("done")

`def empty_text(s: str) ‑> bool`
  Expand source code

      def empty_text(s: str) -> bool:
          return s.strip() == ""

`def run_gui() ‑> None`
  Expand source code

      def run_gui() -> None:
          app = QApplication(sys.argv)
          form = DataDownloadGui()
          form.show()
          sys.exit(app.exec_())

## Classes

`class ApiSelectionWidget (parent=None)`

  QWidget(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  init(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  Initialize self. See help(type(self)) for accurate signature.

  Expand source code

      class ApiSelectionWidget(QWidget):
          def __init__(self, parent=None):
              super(ApiSelectionWidget, self).__init__(parent)

              self.api_900_1000: QRadioButton = QRadioButton()
              self.api_900_1000.setText("API 900 and 1000")
              self.api_900: QRadioButton = QRadioButton()
              self.api_900.setText("API 900")
              self.api_1000: QRadioButton = QRadioButton()
              self.api_1000.setText("API 1000")

              self.api_900_1000.setChecked(True)

              layout: QHBoxLayout = QHBoxLayout()
              layout.addWidget(self.api_900_1000)
              layout.addWidget(self.api_900)
              layout.addWidget(self.api_1000)

              self.setLayout(layout)

  ### Ancestors

  - PySide6.QtWidgets.QWidget
  - PySide6.QtCore.QObject
  - PySide6.QtGui.QPaintDevice
  - Shiboken.Object

  ### Class variables

  `var staticMetaObject`
`class AuthenticationWidget (redvox_config: Optional[RedVoxConfig], parent=None)`

  QWidget(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  init(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  Initialize self. See help(type(self)) for accurate signature.

  Expand source code

      class AuthenticationWidget(QWidget):
          def __init__(self, redvox_config: Optional[RedVoxConfig], parent=None):
              super(AuthenticationWidget, self).__init__(parent)

              self.username: QLineEdit = QLineEdit()
              self.password: QLineEdit = QLineEdit()
              self.password.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)

              if redvox_config is not None:
                  self.username.setText(redvox_config.username)
                  self.password.setText(redvox_config.password)

              layout: QHBoxLayout = QHBoxLayout()

              layout.addWidget(QLabel("Username"))
              layout.addWidget(self.username)

              layout.addWidget(QLabel("Password"))
              layout.addWidget(self.password)

              self.setLayout(layout)

          def validate(self) -> List[str]:
              errors: List[str] = []
              if empty_text(self.username.text()):
                  errors.append("Username must be provided")
              if empty_text(self.password.text()):
                  errors.append("Password must be provided")
              return errors

  ### Ancestors

  - PySide6.QtWidgets.QWidget
  - PySide6.QtCore.QObject
  - PySide6.QtGui.QPaintDevice
  - Shiboken.Object

  ### Class variables

  `var staticMetaObject`

  ### Methods

  `def validate(self) ‑> List[str]`
    Expand source code

        def validate(self) -> List[str]:
            errors: List[str] = []
            if empty_text(self.username.text()):
                errors.append("Username must be provided")
            if empty_text(self.password.text()):
                errors.append("Password must be provided")
            return errors

`class DataDownloadGui (parent=None)`

  QDialog(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  init(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  Initialize self. See help(type(self)) for accurate signature.

  Expand source code

      class DataDownloadGui(QDialog):
          def __init__(self, parent=None):
              super(DataDownloadGui, self).__init__(parent)
              self.setWindowTitle("RedVox Cloud Data Downloader")
              self.setMinimumWidth(1600)
              self.setMinimumHeight(900)

              redvox_config: Optional[RedVoxConfig] = RedVoxConfig.find()

              self.authentication_widget: AuthenticationWidget = AuthenticationWidget(
                  redvox_config
              )
              self.server_selection_widget: ServerSelectionWidget = ServerSelectionWidget(
                  redvox_config
              )
              self.window_selection_widget: WindowSelectionWidget = (
                  WindowSelectionWidget()
              )
              self.station_ids_widget: QTextEdit = QTextEdit()
              self.api_selection_widget: ApiSelectionWidget = ApiSelectionWidget()
              self.output_directory_widget: OutputDirectoryWidget = (
                  OutputDirectoryWidget()
              )
              self.download_data_btn: QPushButton = QPushButton("Download Data")
              # noinspection PyUnresolvedReferences
              self.download_data_btn.clicked.connect(self.download_data)

              self.log: QTextEdit = QTextEdit()
              self.log.setReadOnly(True)
              self.info = lambda msg: self.log.append(
                  f"{datetime.datetime.utcnow()}: {msg}"
              )

              layout: QVBoxLayout = QVBoxLayout()

              layout.addWidget(QLabel("RedVox Authentication"))
              layout.addWidget(self.authentication_widget)

              layout.addWidget(QLabel("Server Selection"))
              layout.addWidget(self.server_selection_widget)

              layout.addWidget(QLabel("Data Window Selection"))
              layout.addWidget(self.window_selection_widget)

              layout.addWidget(QLabel("Station IDs (one per line)"))
              layout.addWidget(self.station_ids_widget)

              layout.addWidget(QLabel("API Selection"))
              layout.addWidget(self.api_selection_widget)

              layout.addWidget(QLabel("Output Directory"))
              layout.addWidget(self.output_directory_widget)

              layout.addWidget(self.download_data_btn)

              layout.addWidget(QLabel("Log"))
              layout.addWidget(self.log)

              self.setLayout(layout)

          def download_data(self):
              validation_errors: List[str] = self.validate()
              if len(validation_errors) > 0:
                  self.info("Errors encountered")
                  for error in validation_errors:
                      self.info(error)
                  return

              username: str = self.authentication_widget.username.text()
              password: str = self.authentication_widget.password.text()
              protocol: str
              if self.server_selection_widget.https.isChecked():
                  protocol = "https"
              else:
                  protocol = "http"
              host: str = self.server_selection_widget.host.text()
              port: int = int(self.server_selection_widget.port.text())
              out_dir: str = self.output_directory_widget.output_dir.text()
              redvox_config: RedVoxConfig = RedVoxConfig(
                  username, password, protocol, host, port
              )
              req_start: int = int(
                  self.window_selection_widget.start_dt.dateTime().toSecsSinceEpoch()
              )
              req_end: int = int(
                  self.window_selection_widget.end_dt.dateTime().toSecsSinceEpoch()
              )
              station_ids: List[str] = list(map(str.strip, self.station_ids_widget.toPlainText().splitlines(
                  False
              )))
              api_type: DataRangeReqType
              if self.api_selection_widget.api_900_1000.isChecked():
                  # noinspection PyTypeChecker
                  api_type = DataRangeReqType.API_900_1000
              elif self.api_selection_widget.api_1000.isChecked():
                  # noinspection PyTypeChecker
                  api_type = DataRangeReqType.API_1000
              else:
                  # noinspection PyTypeChecker
                  api_type = DataRangeReqType.API_900

              retries: int = int(self.server_selection_widget.retries.text())
              timeout: int = int(self.server_selection_widget.timeout.text())
              correct_query_timing: bool = (
                  not self.server_selection_widget.disable_timing_correction.isChecked()
              )

              manager = Manager()
              out_queue = manager.Queue(1024)

              self.info("Starting data query")

              process = Process(
                  target=download_process,
                  args=(
                      redvox_config,
                      timeout,
                      req_start,
                      req_end,
                      station_ids,
                      api_type,
                      correct_query_timing,
                      out_dir,
                      retries,
                      out_queue,
                  ),
              )

              process.start()

              result: str = ""
              while result != "done":
                  # noinspection PyBroadException
                  try:
                      result = out_queue.get(block=True, timeout=0.1)
                      self.info(result)
                      QApplication.processEvents()
                  except:
                      pass

          def validate(self) -> List[str]:
              errors: List[str] = []

              station_ids: List[str] = self.station_ids_widget.toPlainText().splitlines()
              if len(station_ids) == 0:
                  errors.append("At least one station ID must be provided")

              for station_id in station_ids:
                  try:
                      int(station_id)
                  except ValueError:
                      errors.append(f"{station_id} does not appear to be a valid station")

              for widget in [
                  self.window_selection_widget,
                  self.authentication_widget,
                  self.server_selection_widget,
                  self.output_directory_widget,
              ]:
                  # noinspection Mypy
                  errors.extend(widget.validate())

              return errors

  ### Ancestors

  - PySide6.QtWidgets.QDialog
  - PySide6.QtWidgets.QWidget
  - PySide6.QtCore.QObject
  - PySide6.QtGui.QPaintDevice
  - Shiboken.Object

  ### Class variables

  `var staticMetaObject`

  ### Methods

  `def download_data(self)`
    Expand source code

        def download_data(self):
            validation_errors: List[str] = self.validate()
            if len(validation_errors) > 0:
                self.info("Errors encountered")
                for error in validation_errors:
                    self.info(error)
                return

            username: str = self.authentication_widget.username.text()
            password: str = self.authentication_widget.password.text()
            protocol: str
            if self.server_selection_widget.https.isChecked():
                protocol = "https"
            else:
                protocol = "http"
            host: str = self.server_selection_widget.host.text()
            port: int = int(self.server_selection_widget.port.text())
            out_dir: str = self.output_directory_widget.output_dir.text()
            redvox_config: RedVoxConfig = RedVoxConfig(
                username, password, protocol, host, port
            )
            req_start: int = int(
                self.window_selection_widget.start_dt.dateTime().toSecsSinceEpoch()
            )
            req_end: int = int(
                self.window_selection_widget.end_dt.dateTime().toSecsSinceEpoch()
            )
            station_ids: List[str] = list(map(str.strip, self.station_ids_widget.toPlainText().splitlines(
                False
            )))
            api_type: DataRangeReqType
            if self.api_selection_widget.api_900_1000.isChecked():
                # noinspection PyTypeChecker
                api_type = DataRangeReqType.API_900_1000
            elif self.api_selection_widget.api_1000.isChecked():
                # noinspection PyTypeChecker
                api_type = DataRangeReqType.API_1000
            else:
                # noinspection PyTypeChecker
                api_type = DataRangeReqType.API_900

            retries: int = int(self.server_selection_widget.retries.text())
            timeout: int = int(self.server_selection_widget.timeout.text())
            correct_query_timing: bool = (
                not self.server_selection_widget.disable_timing_correction.isChecked()
            )

            manager = Manager()
            out_queue = manager.Queue(1024)

            self.info("Starting data query")

            process = Process(
                target=download_process,
                args=(
                    redvox_config,
                    timeout,
                    req_start,
                    req_end,
                    station_ids,
                    api_type,
                    correct_query_timing,
                    out_dir,
                    retries,
                    out_queue,
                ),
            )

            process.start()

            result: str = ""
            while result != "done":
                # noinspection PyBroadException
                try:
                    result = out_queue.get(block=True, timeout=0.1)
                    self.info(result)
                    QApplication.processEvents()
                except:
                    pass

  `def validate(self) ‑> List[str]`
    Expand source code

        def validate(self) -> List[str]:
            errors: List[str] = []

            station_ids: List[str] = self.station_ids_widget.toPlainText().splitlines()
            if len(station_ids) == 0:
                errors.append("At least one station ID must be provided")

            for station_id in station_ids:
                try:
                    int(station_id)
                except ValueError:
                    errors.append(f"{station_id} does not appear to be a valid station")

            for widget in [
                self.window_selection_widget,
                self.authentication_widget,
                self.server_selection_widget,
                self.output_directory_widget,
            ]:
                # noinspection Mypy
                errors.extend(widget.validate())

            return errors

`class OutputDirectoryWidget (parent=None)`

  QWidget(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  init(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  Initialize self. See help(type(self)) for accurate signature.

  Expand source code

      class OutputDirectoryWidget(QWidget):
          def __init__(self, parent=None):
              super(OutputDirectoryWidget, self).__init__(parent)

              self.output_dir: QLineEdit = QLineEdit()
              self.open_file: QPushButton = QPushButton("Select")

              # noinspection PyUnresolvedReferences
              self.open_file.clicked.connect(self.open_dialog)

              self.output_dir.setText(".")

              layout: QHBoxLayout = QHBoxLayout()
              layout.addWidget(self.output_dir)
              layout.addWidget(self.open_file)

              self.setLayout(layout)

          def open_dialog(self):
              self.output_dir.setText(
                  QFileDialog.getExistingDirectory(options=QFileDialog.ShowDirsOnly)
              )

          def validate(self) -> List[str]:
              errors: List[str] = []

              if empty_text(self.output_dir.text()):
                  errors.append("Output directory must be set")

              if not os.path.isdir(self.output_dir.text()):
                  errors.append("Output directory does not exist")

              return errors

  ### Ancestors

  - PySide6.QtWidgets.QWidget
  - PySide6.QtCore.QObject
  - PySide6.QtGui.QPaintDevice
  - Shiboken.Object

  ### Class variables

  `var staticMetaObject`

  ### Methods

  `def open_dialog(self)`
    Expand source code

        def open_dialog(self):
            self.output_dir.setText(
                QFileDialog.getExistingDirectory(options=QFileDialog.ShowDirsOnly)
            )

  `def validate(self) ‑> List[str]`
    Expand source code

        def validate(self) -> List[str]:
            errors: List[str] = []

            if empty_text(self.output_dir.text()):
                errors.append("Output directory must be set")

            if not os.path.isdir(self.output_dir.text()):
                errors.append("Output directory does not exist")

            return errors

`class ServerSelectionWidget (redvox_config: Optional[RedVoxConfig], parent=None)`

  QWidget(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  init(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  Initialize self. See help(type(self)) for accurate signature.

  Expand source code

      class ServerSelectionWidget(QWidget):
          def __init__(self, redvox_config: Optional[RedVoxConfig], parent=None):
              super(ServerSelectionWidget, self).__init__(parent)

              int_validator = QIntValidator()

              self.https: QRadioButton = QRadioButton()
              self.https.setText("HTTPS")
              self.http: QRadioButton = QRadioButton()
              self.http.setText("HTTP")

              self.host: QLineEdit = QLineEdit()
              self.port: QLineEdit = QLineEdit()
              self.port.setValidator(int_validator)

              self.timeout: QLineEdit = QLineEdit()
              self.retries: QLineEdit = QLineEdit()

              self.timeout.setValidator(int_validator)
              self.retries.setValidator(int_validator)

              self.disable_timing_correction: QCheckBox = QCheckBox()

              if redvox_config is not None:
                  self.host.setText(redvox_config.host)
                  self.port.setText(str(redvox_config.port))
                  if redvox_config.protocol == "http":
                      self.http.setChecked(True)
                  else:
                      self.https.setChecked(True)
              else:
                  self.https.setChecked(True)
                  self.host.setText("redvox.io")
                  self.port.setText("8080")

              self.timeout.setText("120")
              self.retries.setText("3")

              self.disable_timing_correction.setText("Disable Timing Correction")
              self.disable_timing_correction.setChecked(False)

              layout: QVBoxLayout = QVBoxLayout()
              server_widget: QWidget = QWidget()
              server_options_widget: QWidget = QWidget()
              server_layout: QHBoxLayout = QHBoxLayout()
              server_options_layout: QHBoxLayout = QHBoxLayout()
              server_widget.setLayout(server_layout)
              server_options_widget.setLayout(server_options_layout)

              server_layout.addWidget(QLabel("Protocol"))
              server_layout.addWidget(self.https)
              server_layout.addWidget(self.http)
              server_layout.addWidget(QLabel("Host"))
              server_layout.addWidget(self.host)
              server_layout.addWidget(QLabel("Port"))
              server_layout.addWidget(self.port)

              server_options_layout.addWidget(QLabel("Timeout"))
              server_options_layout.addWidget(self.timeout)
              server_options_layout.addWidget(QLabel("Retries"))
              server_options_layout.addWidget(self.retries)
              server_options_layout.addWidget(self.disable_timing_correction)

              layout.addWidget(server_widget)
              layout.addWidget(server_options_widget)

              self.setLayout(layout)

          def validate(self) -> List[str]:
              errors: List[str] = []
              if empty_text(self.host.text()):
                  errors.append("Host must be provided")
              if empty_text(self.port.text()):
                  errors.append("Port must be provided")
              if empty_text(self.timeout.text()):
                  errors.append("Timeout must be provided")
              if empty_text(self.retries.text()):
                  errors.append("Retries must be provided")
              return errors

  ### Ancestors

  - PySide6.QtWidgets.QWidget
  - PySide6.QtCore.QObject
  - PySide6.QtGui.QPaintDevice
  - Shiboken.Object

  ### Class variables

  `var staticMetaObject`

  ### Methods

  `def validate(self) ‑> List[str]`
    Expand source code

        def validate(self) -> List[str]:
            errors: List[str] = []
            if empty_text(self.host.text()):
                errors.append("Host must be provided")
            if empty_text(self.port.text()):
                errors.append("Port must be provided")
            if empty_text(self.timeout.text()):
                errors.append("Timeout must be provided")
            if empty_text(self.retries.text()):
                errors.append("Retries must be provided")
            return errors

`class WindowSelectionWidget (parent=None)`

  QWidget(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  init(self, parent: Optional[PySide6.QtWidgets.QWidget] = None, f: PySide6.QtCore.Qt.WindowFlags = Default(Qt.WindowFlags)) -> None

  Initialize self. See help(type(self)) for accurate signature.

  Expand source code

      class WindowSelectionWidget(QWidget):
          def __init__(self, parent=None):
              super(WindowSelectionWidget, self).__init__(parent)

              self.start_dt: QDateTimeEdit = QDateTimeEdit()
              self.end_dt: QDateTimeEdit = QDateTimeEdit()

              now: datetime.datetime = datetime.datetime.now()
              fifteen_minutes_ago: datetime.datetime = now - datetime.timedelta(
                  minutes=15
              )
              date_fmt: str = "yyyy-MM-dd HH:mm UTC"

              self.start_dt.setDisplayFormat(date_fmt)
              self.start_dt.setTimeSpec(PySide6.QtCore.Qt.UTC)
              self.end_dt.setDisplayFormat(date_fmt)
              self.end_dt.setTimeSpec(PySide6.QtCore.Qt.UTC)
              self.start_dt.setCalendarPopup(True)
              self.end_dt.setCalendarPopup(True)
              self.start_dt.setDateTime(fifteen_minutes_ago)
              self.end_dt.setDateTime(now)

              layout: QHBoxLayout = QHBoxLayout()

              layout.addWidget(QLabel("Start"))
              layout.addWidget(self.start_dt)
              layout.addWidget(QLabel("End"))
              layout.addWidget(self.end_dt)

              self.setLayout(layout)

          def validate(self) -> List[str]:
              errors: List[str] = []
              if self.start_dt.dateTime() >= self.end_dt.dateTime():
                  errors.append(
                      "Start of time window must be strictly less than end of time window"
                  )
              return errors

  ### Ancestors

  - PySide6.QtWidgets.QWidget
  - PySide6.QtCore.QObject
  - PySide6.QtGui.QPaintDevice
  - Shiboken.Object

  ### Class variables

  `var staticMetaObject`

  ### Methods

  `def validate(self) ‑> List[str]`
    Expand source code

        def validate(self) -> List[str]:
            errors: List[str] = []
            if self.start_dt.dateTime() >= self.end_dt.dateTime():
                errors.append(
                    "Start of time window must be strictly less than end of time window"
                )
            return errors