diff --git a/zukeUI/__init__.py b/zukeUI/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/zukeUI/assets/config.json b/zukeUI/assets/config.json
new file mode 100644
index 0000000..abd1dd8
--- /dev/null
+++ b/zukeUI/assets/config.json
@@ -0,0 +1,5 @@
+{
+"zukebox_user": "MOHI",
+"zukebox_ip": "10.30.255.175",
+"zukebox_port": "5000"
+}
diff --git a/zukeUI/assets/default.jpg b/zukeUI/assets/default.jpg
new file mode 100644
index 0000000..33e2794
Binary files /dev/null and b/zukeUI/assets/default.jpg differ
diff --git a/zukeUI/assets/zukeUI.ui b/zukeUI/assets/zukeUI.ui
new file mode 100644
index 0000000..d2fa9f7
--- /dev/null
+++ b/zukeUI/assets/zukeUI.ui
@@ -0,0 +1,305 @@
+
+
+ Form
+
+
+ Qt::ApplicationModal
+
+
+
+ 0
+ 0
+ 569
+ 708
+
+
+
+ Qt::DefaultContextMenu
+
+
+ ZukeUI
+
+
+
+ zukebox.jpgzukebox.jpg
+
+
+
+
+ 40
+ 50
+ 491
+ 311
+
+
+
+ -
+
+
+ QFrame::Sunken
+
+
+
+
+
+ default.jpg
+
+
+ true
+
+
+
+
+
+
+
+
+ 30
+ 9
+ 511
+ 31
+
+
+
+
+ 12
+
+
+
+
+
+
+ Qt::AlignCenter
+
+
+ true
+
+
+
+
+
+ 40
+ 480
+ 491
+ 171
+
+
+
+ QFrame::WinPanel
+
+
+ QFrame::Plain
+
+
+
+
+ 20
+ 10
+ 461
+ 31
+
+
+
+ URL:
+
+
+
+
+
+ 20
+ 50
+ 461
+ 31
+
+
+
+ Message:
+
+
+
+
+
+ 10
+ 90
+ 471
+ 71
+
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+ -
+
+
+ Play/Pause
+
+
+
+ -
+
+
+ Send
+
+
+
+
+
+
+
+
+
+ 130
+ 410
+ 381
+ 20
+
+
+
+
+
+
+ false
+
+
+ Qt::Horizontal
+
+
+
+
+
+ 130
+ 440
+ 381
+ 20
+
+
+
+
+
+
+ 100
+
+
+ false
+
+
+ Qt::Horizontal
+
+
+
+
+
+ 142
+ 380
+ 381
+ 21
+
+
+
+
+ DejaVu Sans
+ 12
+ 50
+ false
+ false
+
+
+
+
+
+
+ Qt::AlignCenter
+
+
+ true
+
+
+
+
+
+ 60
+ 670
+ 441
+ 31
+
+
+
+
+ 12
+ 75
+ true
+
+
+
+
+
+
+ Qt::AlignCenter
+
+
+
+
+
+ 65
+ 410
+ 61
+ 20
+
+
+
+ Volume:
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+
+
+ 65
+ 440
+ 61
+ 20
+
+
+
+ Seek:
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+
+
+ 65
+ 380
+ 61
+ 21
+
+
+
+ Sent by:
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ frame_2
+ widget
+ track_title
+ volume_slider
+ seek_slider
+ track_sender
+ error_message
+ volume_label
+ label_2
+ sentby_label
+
+
+
+
diff --git a/zukeUI/assets/zukebox.jpg b/zukeUI/assets/zukebox.jpg
new file mode 100644
index 0000000..1b6a4ed
Binary files /dev/null and b/zukeUI/assets/zukebox.jpg differ
diff --git a/zukeUI/assets/zukebox.svg b/zukeUI/assets/zukebox.svg
new file mode 100644
index 0000000..720a50c
--- /dev/null
+++ b/zukeUI/assets/zukebox.svg
@@ -0,0 +1,3 @@
+
diff --git a/zukeUI/driver/__init__.py b/zukeUI/driver/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/zukeUI/driver/__pycache__/__init__.cpython-35.pyc b/zukeUI/driver/__pycache__/__init__.cpython-35.pyc
new file mode 100644
index 0000000..f9ef826
Binary files /dev/null and b/zukeUI/driver/__pycache__/__init__.cpython-35.pyc differ
diff --git a/zukeUI/driver/driver.iml b/zukeUI/driver/driver.iml
new file mode 100644
index 0000000..19dbd15
--- /dev/null
+++ b/zukeUI/driver/driver.iml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zukeUI/driver/utils/__init__.py b/zukeUI/driver/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/zukeUI/driver/utils/__pycache__/__init__.cpython-35.pyc b/zukeUI/driver/utils/__pycache__/__init__.cpython-35.pyc
new file mode 100644
index 0000000..5915eed
Binary files /dev/null and b/zukeUI/driver/utils/__pycache__/__init__.cpython-35.pyc differ
diff --git a/zukeUI/driver/utils/configuration/__init__.py b/zukeUI/driver/utils/configuration/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/zukeUI/driver/utils/configuration/__pycache__/__init__.cpython-35.pyc b/zukeUI/driver/utils/configuration/__pycache__/__init__.cpython-35.pyc
new file mode 100644
index 0000000..bd2ddeb
Binary files /dev/null and b/zukeUI/driver/utils/configuration/__pycache__/__init__.cpython-35.pyc differ
diff --git a/zukeUI/driver/utils/configuration/__pycache__/zukeconfiguration.cpython-35.pyc b/zukeUI/driver/utils/configuration/__pycache__/zukeconfiguration.cpython-35.pyc
new file mode 100644
index 0000000..a984579
Binary files /dev/null and b/zukeUI/driver/utils/configuration/__pycache__/zukeconfiguration.cpython-35.pyc differ
diff --git a/zukeUI/driver/utils/configuration/zukeconfiguration.py b/zukeUI/driver/utils/configuration/zukeconfiguration.py
new file mode 100644
index 0000000..510cd6a
--- /dev/null
+++ b/zukeUI/driver/utils/configuration/zukeconfiguration.py
@@ -0,0 +1,74 @@
+import json
+import os
+from json import JSONDecodeError
+
+
+class ZukeConfigNotExistsError(Exception):
+ pass
+
+
+class InvalidZukeConfigFormatError(Exception):
+ pass
+
+
+class InvalidZukeConfigError(Exception):
+ pass
+
+_ZUKE_IP = "zukebox_ip"
+_ZUKE_PORT = "zukebox_port"
+_ZUKE_USER = "zukebox_user"
+
+class ZukeConfigManager:
+
+ def __init__(self, config_path: str):
+ self.__config_path = config_path
+ self.__config = {}
+
+ def init_config(self):
+ self.__config = ZukeConfigInitializer.init_config(self.__config_path)
+
+ @property
+ def config(self):
+ return self.__config
+
+ @property
+ def zuke_ip(self):
+ return self.__config[_ZUKE_IP]
+
+ @property
+ def zuke_port(self):
+ return self.__config[_ZUKE_PORT]
+
+ @property
+ def zuke_user(self):
+ return self.__config[_ZUKE_USER]
+
+ @property
+ def config_keys(self):
+ return self.config.keys()
+
+
+class ZukeConfigInitializer:
+
+ @staticmethod
+ def init_config(config_path: str) -> dict:
+ try:
+ config = json.load(open(config_path, 'r'))
+ ZukeConfigValidator.validate_zuke_config(config)
+ return config
+ except JSONDecodeError:
+ raise InvalidZukeConfigFormatError("Invalid zuke configuration format (not json)")
+ except IOError:
+ raise ZukeConfigNotExistsError("Zuke configuration not exists {path}".format(path=config_path))
+
+
+class ZukeConfigValidator:
+
+ @staticmethod
+ def validate_zuke_config(config: dict):
+ if sorted(
+ (_ZUKE_USER,
+ _ZUKE_PORT,
+ _ZUKE_IP)
+ ) != sorted(config.keys()):
+ raise InvalidZukeConfigError("Config contains invalid keys")
diff --git a/zukeUI/driver/utils/connection/__init__.py b/zukeUI/driver/utils/connection/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/zukeUI/driver/utils/connection/__pycache__/__init__.cpython-35.pyc b/zukeUI/driver/utils/connection/__pycache__/__init__.cpython-35.pyc
new file mode 100644
index 0000000..a431554
Binary files /dev/null and b/zukeUI/driver/utils/connection/__pycache__/__init__.cpython-35.pyc differ
diff --git a/zukeUI/driver/utils/connection/__pycache__/request_managing.cpython-35.pyc b/zukeUI/driver/utils/connection/__pycache__/request_managing.cpython-35.pyc
new file mode 100644
index 0000000..e172e8b
Binary files /dev/null and b/zukeUI/driver/utils/connection/__pycache__/request_managing.cpython-35.pyc differ
diff --git a/zukeUI/driver/utils/connection/__pycache__/zukeconnection.cpython-35.pyc b/zukeUI/driver/utils/connection/__pycache__/zukeconnection.cpython-35.pyc
new file mode 100644
index 0000000..a9a3b24
Binary files /dev/null and b/zukeUI/driver/utils/connection/__pycache__/zukeconnection.cpython-35.pyc differ
diff --git a/zukeUI/driver/utils/connection/__pycache__/zukerequest_managing.cpython-35.pyc b/zukeUI/driver/utils/connection/__pycache__/zukerequest_managing.cpython-35.pyc
new file mode 100644
index 0000000..39162d0
Binary files /dev/null and b/zukeUI/driver/utils/connection/__pycache__/zukerequest_managing.cpython-35.pyc differ
diff --git a/zukeUI/driver/utils/connection/request_managing.py b/zukeUI/driver/utils/connection/request_managing.py
new file mode 100644
index 0000000..0a75e1c
--- /dev/null
+++ b/zukeUI/driver/utils/connection/request_managing.py
@@ -0,0 +1,118 @@
+import json
+
+import time
+from PyQt5 import QtCore
+
+from driver.utils.configuration.zukeconfiguration import ZukeConfigManager
+from driver.utils.connection.zukeconnection import ZukeRequest, ZukeRequestThread
+
+
+class RequestManager(QtCore.QThread):
+
+ def __init__(self, config_manager: ZukeConfigManager):
+ QtCore.QThread.__init__(self)
+ self._config_manager = config_manager
+ self._requests = []
+ self._refreshing = False
+ self._callback = None
+ self._control = None
+
+ def send_request(self, request: ZukeRequest, response_callback=None):
+ request = ZukeRequestThread(request, response_callback)
+ request.start()
+ self._requests.append(request)
+
+ def send_track(self, url, response_callback=None, message: str=None):
+
+ body = {'url': url, 'user': self._config_manager.zuke_user}
+ if message:
+ body.update({'message': message, 'lang': 'hu'})
+ self.send_request(
+ ZukeRequest(
+ self._config_manager.zuke_ip,
+ self._config_manager.zuke_port,
+ "POST",
+ "/player/tracks",
+ body=json.dumps(body),
+ headers={'content-type': 'application/json'}
+ ),
+ response_callback
+ )
+
+ def set_volume(self, volume_value, response_callback=None):
+ self.send_request(
+ ZukeRequest(
+ self._config_manager.zuke_ip,
+ self._config_manager.zuke_port,
+ "PATCH",
+ "/player/control",
+ body=json.dumps({'volume': volume_value}),
+ headers={'content-type': 'application/json'}
+ ),
+ response_callback
+ )
+
+ def set_seek(self, seek_value, response_callback=None):
+ self.send_request(
+ ZukeRequest(
+ self._config_manager.zuke_ip,
+ self._config_manager.zuke_port,
+ "PATCH",
+ "/player/control",
+ body=json.dumps({'time': seek_value}),
+ headers={'content-type': 'application/json'}
+ ),
+ response_callback
+ )
+
+
+ manager_id = "playpause_manager"
+ response_signal = stop_signal = QtCore.pyqtSignal(dict, name=manager_id)
+
+ def play_or_pause(self, play_or_pause, response_callback=None):
+ self.send_request(
+ ZukeRequest(
+ self._config_manager.zuke_ip,
+ self._config_manager.zuke_port,
+ "PATCH",
+ "/player/control",
+ body=json.dumps({'playing': play_or_pause}),
+ headers={'content-type': 'application/json'}
+ ),
+ response_callback
+ )
+
+ def get_control(self, callback=None):
+ self.send_request(
+ ZukeRequest(
+ self._config_manager.zuke_ip,
+ self._config_manager.zuke_port,
+ "GET",
+ "/player/control"
+ ),
+ callback
+ )
+
+ def run(self):
+ while self._refreshing:
+ self.get_control(self._callback)
+ time.sleep(1)
+
+ def _refresh_callback(self, response):
+ self._control = response
+ self._callback(response)
+
+ def start_refreshing(self, callback=None):
+ if not self._refreshing:
+ self._callback = callback
+ self._refreshing = True
+ self.start()
+
+ def stop_refreshing(self):
+ if self._refreshing:
+ self._refreshing = False
+ self.quit()
+
+ @property
+ def control(self):
+ return self._control
\ No newline at end of file
diff --git a/zukeUI/driver/utils/connection/zukeconnection.py b/zukeUI/driver/utils/connection/zukeconnection.py
new file mode 100644
index 0000000..c5066e2
--- /dev/null
+++ b/zukeUI/driver/utils/connection/zukeconnection.py
@@ -0,0 +1,56 @@
+import http.client
+import json
+from PyQt5 import QtCore
+from json import JSONDecodeError
+
+
+class InvalidZukeResponseError(Exception):
+ pass
+
+
+class ZukeAvailabilityError(Exception):
+ pass
+
+
+class ZukeRequest:
+
+ def __init__(self, ip: str, port: str, command: str, url: str, *, body: dict=None, headers: dict=None):
+ self.ip = ip
+ self.port = port
+ self.command = command
+ self.url = url
+ self.body = body if body else ""
+ self.headers = headers if headers else {}
+
+ def do_request(self):
+ conn = http.client.HTTPConnection(self.ip, self.port)
+ try:
+ conn.request(
+ self.command,
+ self.url,
+ self.body,
+ self.headers
+ )
+ return json.loads(conn.getresponse().read().decode('utf-8'))
+ except JSONDecodeError:
+ raise InvalidZukeResponseError("Invalid zukeresponse")
+ except:
+ raise ZukeAvailabilityError("Zukebox unavailable")
+ finally:
+ conn.close()
+
+
+class ZukeRequestThread(QtCore.QThread):
+ response_signal = QtCore.pyqtSignal(dict)
+
+ def __init__(self, zukerequest: ZukeRequest, callback):
+ QtCore.QThread.__init__(self)
+ self._zr = zukerequest
+ self._callback = callback
+
+ def run(self):
+ if self._callback:
+ self.response_signal.connect(self._callback)
+ self.response_signal.emit(self._zr.do_request())
+ else:
+ self._zr.do_request()
diff --git a/zukeUI/runner.sh b/zukeUI/runner.sh
new file mode 100755
index 0000000..64b117a
--- /dev/null
+++ b/zukeUI/runner.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+BASEDIR=$(dirname "$0")
+
+if [[ *"zukeUI"* != $PYTHONPATH ]]; then
+ export PYTHONPATH=${BASEDIR}
+fi
+python3 ./${BASEDIR}/ui/zukeui.py ./${BASEDIR}/assets/
\ No newline at end of file
diff --git a/zukeUI/ui/__init__.py b/zukeUI/ui/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/zukeUI/ui/__pycache__/__init__.cpython-35.pyc b/zukeUI/ui/__pycache__/__init__.cpython-35.pyc
new file mode 100644
index 0000000..e5869cf
Binary files /dev/null and b/zukeUI/ui/__pycache__/__init__.cpython-35.pyc differ
diff --git a/zukeUI/ui/zukeui.py b/zukeUI/ui/zukeui.py
new file mode 100644
index 0000000..4e90ec8
--- /dev/null
+++ b/zukeUI/ui/zukeui.py
@@ -0,0 +1,131 @@
+import os
+import sys
+
+import urllib.request
+
+from PyQt5 import QtWidgets
+
+from PyQt5 import uic
+from PyQt5.QtGui import QPixmap
+from PyQt5.QtWidgets import QApplication
+from PyQt5.QtWidgets import QStyle
+
+from driver.utils.configuration.zukeconfiguration import ZukeConfigManager
+from driver.utils.connection.zukeconnection import ZukeAvailabilityError, InvalidZukeResponseError
+from driver.utils.connection.request_managing import RequestManager
+from zukeui_manager import ZukeUiManager
+
+
+class Ui(QtWidgets.QDialog):
+ def __init__(self, path, zuc: ZukeUiManager):
+ super(Ui, self).__init__()
+ uic.loadUi(path, self)
+ self.__zuc = zuc
+ self.track_picture = self.findChild(QtWidgets.QLabel, 'track_picture')
+ self.track_sender = self.findChild(QtWidgets.QLabel, 'track_sender')
+ self.track_title = self.findChild(QtWidgets.QLabel, 'track_title')
+ self.error_message = self.findChild(QtWidgets.QLabel, 'error_message')
+ self.send_button = self.findChild(QtWidgets.QPushButton, 'send_button')
+ self.playorpause_button = self.findChild(QtWidgets.QPushButton, 'playorpause_button')
+ self.link_edit = self.findChild(QtWidgets.QTextEdit, 'link_edit')
+ self.message_edit = self.findChild(QtWidgets.QTextEdit, 'message_edit')
+ self.volume_slider = self.findChild(QtWidgets.QSlider, 'volume_slider')
+ self.seek_slider = self.findChild(QtWidgets.QSlider, 'seek_slider')
+
+ self.send_button.clicked.connect(self.send_link)
+ self.playorpause_button.clicked.connect(self.play_or_pause)
+ self.volume_slider.valueChanged.connect(self.set_volume)
+ self.seek_slider.valueChanged.connect(self.set_seek)
+ self.__is_refreshing = False
+ self.__prev_title = None
+ self.__prev_url = None
+ try:
+ self.__zuc.start_refreshing(self._refresh_callback)
+ except (ZukeAvailabilityError, InvalidZukeResponseError) as ex:
+ self.__zuc.stop_refreshing()
+ print("Exception during starting refreshing")
+ self.error_message.setText("Error! Ui refreshing turned off")
+ self.__zuc.start_refreshing(self._refresh_callback)
+
+ def __del__(self):
+ self.__zuc.stop_refreshing()
+
+ def send_link(self):
+ try:
+ self.__zuc.send_url(self.link_edit.toPlainText(), self.message_edit.toPlainText(), self._send_callback)
+ except (ZukeAvailabilityError, InvalidZukeResponseError) as ex:
+ self.__zuc.stop_refreshing()
+ print("Exception during sending link")
+ self.error_message.setText("Error! Ui refreshing turned off")
+
+ def _send_callback(self, response):
+ if self.link_edit.toPlainText():
+ self.link_edit.setText("")
+ if self.message_edit.toPlainText():
+ self.message_edit.setText("")
+
+ def play_or_pause(self):
+ try:
+ if self.__zuc.control:
+ self.__zuc.play_or_pause(0 if self.__zuc.control["playing"] else 1)
+ else:
+ self.__zuc.get_control(self.control_to_play_or_pause_callback)
+ except (ZukeAvailabilityError, InvalidZukeResponseError) as ex:
+ self.__zuc.stop_refreshing()
+ print("Exception during play or pause")
+ self.error_message.setText("Error! Ui refreshing turned off")
+
+ def set_volume(self):
+ if not self.__is_refreshing:
+ try:
+ self.__zuc.set_volume(self.volume_slider.sliderPosition())
+ except (ZukeAvailabilityError, InvalidZukeResponseError) as ex:
+ self.__zuc.stop_refreshing()
+ print("Exception during setting volume")
+ self.error_message.setText("Error! Ui refreshing turned off")
+
+ def set_seek(self):
+ if not self.__is_refreshing:
+ try:
+ self.__zuc.set_seek(self.seek_slider.sliderPosition())
+ except (ZukeAvailabilityError, InvalidZukeResponseError) as ex:
+ self.__zuc.stop_refreshing()
+ print("Exception during setting seek")
+ self.error_message.setText("Error! Ui refreshing turned off")
+
+ def _refresh_callback(self, control: dict):
+ track = control.get("track", {})
+ title = track.get("title", None) if track else None
+ if title:
+ if title != self.__prev_title:
+ self.track_picture.setPixmap(QPixmap(urllib.request.urlretrieve(track["thumbnail"])[0]))
+ self.track_sender.setText(track["user"])
+ self.track_title.setText(title)
+ self.__prev_title = title
+ if not self.seek_slider.isSliderDown():
+ self.__is_refreshing = True
+ self.seek_slider.setMaximum(track["duration"])
+ self.seek_slider.setSliderPosition(control["time"])
+ self.__is_refreshing = False
+ if not self.volume_slider.isSliderDown():
+ self.__is_refreshing = True
+ self.volume_slider.setSliderPosition(control["volume"])
+ self.__is_refreshing = False
+
+ def control_to_play_or_pause_callback(self, response):
+ self.__zuc.play_or_pause(0 if response["playing"] else 1)
+
+
+if __name__ == "__main__":
+ assets_path = sys.argv[1]
+ zcm = ZukeConfigManager(os.path.join(assets_path, 'config.json'))
+ zcm.init_config()
+ app = QApplication(sys.argv)
+ w = Ui(
+ os.path.join(assets_path, 'zukeUI.ui'),
+ ZukeUiManager(
+ RequestManager(zcm)
+ )
+ )
+ w.show()
+ sys.exit(app.exec_())
diff --git a/zukeUI/ui/zukeui_manager.py b/zukeUI/ui/zukeui_manager.py
new file mode 100644
index 0000000..b6b3d66
--- /dev/null
+++ b/zukeUI/ui/zukeui_manager.py
@@ -0,0 +1,33 @@
+from driver.utils.connection.request_managing import RequestManager
+
+
+class ZukeUiManager:
+
+ def __init__(self, rm: RequestManager):
+ self.__rm = rm
+
+ def send_url(self, url: str, message: str, callback=None):
+ if url:
+ self.__rm.send_track(url, callback, message)
+
+ def set_volume(self, volume, callback=None):
+ self.__rm.set_volume(volume, callback)
+
+ def set_seek(self, seek, callback=None):
+ self.__rm.set_seek(seek, callback)
+
+ def play_or_pause(self, play_or_pause, callback=None):
+ self.__rm.play_or_pause(play_or_pause, callback)
+
+ def stop_refreshing(self):
+ self.__rm.stop_refreshing()
+
+ def start_refreshing(self, ui_callback):
+ self.__rm.start_refreshing(ui_callback)
+
+ def get_control(self, callback=None):
+ self.__rm.get_control(callback)
+
+ @property
+ def control(self):
+ return self.__rm.control
diff --git a/zukeUI/zukeUI.iml b/zukeUI/zukeUI.iml
new file mode 100644
index 0000000..c2feb10
--- /dev/null
+++ b/zukeUI/zukeUI.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file