From 0ee9b334a0c431738e38093b697a33ce45068f08 Mon Sep 17 00:00:00 2001 From: Peter Mohos Date: Wed, 24 Aug 2016 17:01:22 +0200 Subject: [PATCH 1/4] zukeUI: added --- zukeUI/.idea/workspace.xml | 810 ++++++++++++++++++ zukeUI/__init__.py | 0 zukeUI/run_zukeui.py | 170 ++++ zukeUI/sources/config.txt | 3 + zukeUI/sources/default.jpg | Bin 0 -> 17549 bytes zukeUI/sources/zukebox.jpg | Bin 0 -> 20918 bytes zukeUI/sources/zukebox.svg | 3 + zukeUI/tests/__init__.py | 0 zukeUI/tests/test_zukedriver.py | 18 + zukeUI/utils/__init__.py | 0 .../utils/__pycache__/__init__.cpython-35.pyc | Bin 0 -> 137 bytes .../__pycache__/zukedriver.cpython-35.pyc | Bin 0 -> 3366 bytes zukeUI/utils/configuration/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 0 -> 151 bytes .../zukeconfigmanager.cpython-35.pyc | Bin 0 -> 1534 bytes .../utils/configuration/zukeconfigmanager.py | 29 + zukeUI/utils/connection/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 0 -> 148 bytes .../__pycache__/zukeconnector.cpython-35.pyc | Bin 0 -> 999 bytes zukeUI/utils/connection/zukeconnector.py | 20 + zukeUI/utils/threadhandling/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 0 -> 152 bytes .../threaddecorators.cpython-35.pyc | Bin 0 -> 841 bytes .../utils/threadhandling/threaddecorators.py | 17 + zukeUI/utils/zukedriver.py | 72 ++ 25 files changed, 1142 insertions(+) create mode 100644 zukeUI/.idea/workspace.xml create mode 100644 zukeUI/__init__.py create mode 100644 zukeUI/run_zukeui.py create mode 100644 zukeUI/sources/config.txt create mode 100644 zukeUI/sources/default.jpg create mode 100644 zukeUI/sources/zukebox.jpg create mode 100644 zukeUI/sources/zukebox.svg create mode 100644 zukeUI/tests/__init__.py create mode 100644 zukeUI/tests/test_zukedriver.py create mode 100644 zukeUI/utils/__init__.py create mode 100644 zukeUI/utils/__pycache__/__init__.cpython-35.pyc create mode 100644 zukeUI/utils/__pycache__/zukedriver.cpython-35.pyc create mode 100644 zukeUI/utils/configuration/__init__.py create mode 100644 zukeUI/utils/configuration/__pycache__/__init__.cpython-35.pyc create mode 100644 zukeUI/utils/configuration/__pycache__/zukeconfigmanager.cpython-35.pyc create mode 100644 zukeUI/utils/configuration/zukeconfigmanager.py create mode 100644 zukeUI/utils/connection/__init__.py create mode 100644 zukeUI/utils/connection/__pycache__/__init__.cpython-35.pyc create mode 100644 zukeUI/utils/connection/__pycache__/zukeconnector.cpython-35.pyc create mode 100644 zukeUI/utils/connection/zukeconnector.py create mode 100644 zukeUI/utils/threadhandling/__init__.py create mode 100644 zukeUI/utils/threadhandling/__pycache__/__init__.cpython-35.pyc create mode 100644 zukeUI/utils/threadhandling/__pycache__/threaddecorators.cpython-35.pyc create mode 100644 zukeUI/utils/threadhandling/threaddecorators.py create mode 100644 zukeUI/utils/zukedriver.py diff --git a/zukeUI/.idea/workspace.xml b/zukeUI/.idea/workspace.xml new file mode 100644 index 0000000..3db62cd --- /dev/null +++ b/zukeUI/.idea/workspace.xml @@ -0,0 +1,810 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1472036699249 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/zukeUI/__init__.py b/zukeUI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zukeUI/run_zukeui.py b/zukeUI/run_zukeui.py new file mode 100644 index 0000000..d157142 --- /dev/null +++ b/zukeUI/run_zukeui.py @@ -0,0 +1,170 @@ +import base64 +import io +import os +import tkinter as tk +import urllib.request + +import time + +from utils.threadhandling.threaddecorators import thread, daemon +from utils.zukedriver import ZukeDriver +from PIL import Image, ImageTk + +class ZukeUi(tk.Frame): + def __init__(self, root): + tk.Frame.__init__(self,root) + self.after_initialization = False + self.zd = ZukeDriver() + control = self.zd.get_control() + self.init_ui(root, control) + self.refresh = True + self.refresh_seekscale = True + self.refresh_volumescale = True + self.refresher() + + self.pack(fill="both", expand="true") + + self.after_initialization = True + + + def __del__(self): + self.refresh = False + + def init_ui(self,root,control): + self.root = root + self.root.title("ZukeUi") + self.root.iconphoto(True,ImageTk.PhotoImage(file='sources/zukebox.jpg')) + self.actual_track_title = "" + self.pic_and_title_frame = tk.Frame(self) + self.pic_and_title_frame.pack(fill="x") + opened_img = Image.open(os.path.join(os.getcwd(), 'sources/default.jpg')) + opened_img = opened_img.resize((350, 350), Image.PERSPECTIVE) + self.actual_track_image = ImageTk.PhotoImage(opened_img) + self.track_img_label = tk.Label(self.pic_and_title_frame, image=self.actual_track_image) + self.track_img_label.pack(padx=5, pady=5) + self.track_img_label.image = self.actual_track_image + self.track_title_label = tk.Label(self.pic_and_title_frame, text="") + self.track_title_label.pack() + + self.sender_frame = tk.Frame(self) + self.sender_frame.pack(fill="x") + self.sender_label = tk.Label(self.sender_frame, text="Sender:",width=10) + self.sender_label.pack(side="left",padx=5, pady=5) + self.name_label = tk.Label(self.sender_frame) + self.name_label.pack(fill="x",side="right",padx=5, pady=5, expand="true") + + self.url_frame = tk.Frame(self) + self.url_frame.pack(fill="x") + self.url_label = tk.Label(self.url_frame, text="URL:",width=10) + self.url_label.pack(side="left",padx=5,pady=5) + self.url_entry= tk.Entry(self.url_frame) + self.url_entry.pack(fill="x",side="right",padx=5,pady=5,expand="true") + + self.message_frame = tk.Frame(self) + self.message_frame.pack(fill="x") + self.message_label = tk.Label(self.message_frame, text="Message:",width=10) + self.message_label.pack(side="left",padx=5, pady=5) + self.message_entry = tk.Entry(self.message_frame) + self.message_entry.pack(fill="x",side="right",padx=5, pady=5, expand="true") + + + self.volume_set_frame = tk.Frame(self) + self.volume_set_frame.pack(fill="x") + self.volume_label = tk.Label(self.volume_set_frame, text="Volume:", width=10) + self.volume_label.pack(side="left",padx=5,pady=5) + + self.volume_setter = \ + tk.Scale(self.volume_set_frame, from_=0, to=100, command=self.set_volume, orient="horizontal", length=200) + self.volume_setter.pack(side="right",fill="x",expand="true",padx=5,pady=5) + self.volume_setter.set(control["volume"]) + + self.seek_set_frame = tk.Frame(self) + self.seek_set_frame.pack(fill="x") + self.seek_label = tk.Label(self.seek_set_frame, text="Seek:", width=10) + self.seek_label.pack(side="left", padx=5, pady=5) + + self.actual_track_duration = 200 + if "duration" in control["track"].keys(): + self.actual_track_duration = control["track"]["duration"] + + self.seek_setter = \ + tk.Scale(self.seek_set_frame, from_=0, to=self.actual_track_duration,command=self.set_seek, orient="horizontal", length=200) + self.seek_setter.pack(side="right", fill="x", expand="true", padx=5, pady=5) + self.seek_setter.set(control["time"]) + + self.button_frame = tk.Frame(self) + self.button_frame.pack(fill="x",expand="true") + + self.playorpause_button = tk.Button(self.button_frame, text="Play/Pause", command=self.play_or_pause) + self.playorpause_button.pack(fill="x") + self.send_button = tk.Button(self.button_frame, text="Send", command=self.send_link_and_message) + self.send_button.pack(fill="x") + + @daemon + def refresher(self): + while self.refresh: + control = self.zd.get_control() + track = control["track"] + self.refresh_volumescale = False + self.volume_setter.set(control["volume"]) + if track and "title" in track.keys(): + self.refresh_seekscale=False + self.seek_setter.set(control["time"]) + if track["title"] != self.actual_track_title: + self.seek_setter.configure(to=track["duration"]) + self.actual_track_title = track["title"] + self.actual_sender = track["user"] + self.actual_track_image = ImageTk.PhotoImage(self.get_pic(track["thumbnail"])) + self.track_img_label.configure(image=self.actual_track_image) + self.track_img_label.image=self.actual_track_image + self.track_title_label.configure(text=self.actual_track_title) + self.name_label.configure(text=self.actual_sender) + time.sleep(1) + + def get_pic(self, image_url): + image, headers = urllib.request.urlretrieve(image_url) + opened_img = Image.open(image) + opened_img = opened_img.resize((350, 350), Image.PERSPECTIVE) + return opened_img + + + def play_or_pause(self): + if self.after_initialization: + if self.zd.get_control()["playing"] == 1: + self.zd.play_or_pause(0) + elif self.zd.get_control()["track"]: + self.zd.play_or_pause(1) + + def send_link_and_message(self): + response = "" + if self.after_initialization: + url = self.url_entry.get() + message = self.message_entry.get() + if url: + if message: + response = self.zd.send_track_with_message(url,message) + self.message_entry.delete(0, 'end') + else: + response = self.zd.send_track(url) + self.url_entry.delete(0, 'end') + + + def set_volume(self, volume): + if self.after_initialization and self.refresh_volumescale: + self.zd.set_volume(volume) + self.refresh_volumescale=True + + def set_seek(self, seek): + if self.after_initialization and self.refresh_seekscale: + self.zd.set_seek(seek) + self.refresh_seekscale=True + + +def main(): + root = tk.Tk() + ZukeUi(root).pack(expand=True, fill='both') + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/zukeUI/sources/config.txt b/zukeUI/sources/config.txt new file mode 100644 index 0000000..4cd0d94 --- /dev/null +++ b/zukeUI/sources/config.txt @@ -0,0 +1,3 @@ +MOHI +10.30.255.175 +5000 diff --git a/zukeUI/sources/default.jpg b/zukeUI/sources/default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..33e2794359c252b8434c1d3b0d4f0507052b4fda GIT binary patch literal 17549 zcmb?>1#lfdljk!tGcz-D%n&m(KQlAiF*C(6GgHjW%zkEynPSIw%*;9G|LyL#SG9Ll zS9Ma&sMYGHr|F)N)Iar~AAfcLsIpR@r2t^y007uW2K+e#geqG(x;eU9IXaPYFtY-9 z#Xc)QLIVCKOG0p3fT@(2f&?oYD=Vvs2`?+hN6E@#&dT?<}INAS^1G2K4{H^nHvH!zoH(@vbr*6ji&+h<#_P_~0i;Ej6t13x-mX`#8 zeT)no(GF*gpabxk09~C#m6hfH8SDReyI=nM zi~zs_)8D@SN0I+C6W+|+75LHo`f(Woom|{LRHpwyBY3(w{iUrwXiQVve`vqIw9Cf; zKlq7%Y0H1n#sA3si*EZzMqO3>Lk9Fgi&*|Q+VsEC|M9;cA^->=``_aKZ*nvD`WQa| zASDI>{MG5ddjGes{>_8H{ulzshb@Z#&2uaS0C?{{_=*4KnPvb0tziHF?%Kb3l=%Pv zN+ zeWqWPb+7-z;4$l;KY!K%;s7{kXjo_{I9M21csMwCBy40PL_{Qf3`|sP5<)Uk5<+5P z3L18L3Mv*VVqyjXMive(US3`@dLdCkZc%m~Uar5LfWg7TBOxH+AtU2)Q4&*f{XeHa z0{}Ewz%E!K1b`d>h6Vt%4S)#oA%+Hy_Hn-}h08iWm-du#69H5%)j_zVOMi1D63cHpzFi(Hc7uu*f>en} z;7T*dkRo)v(Z)Dx(VJR5mCbP!NQUiZBJ9=RRME{eM{|IOZTjqiZ6biBsgiftO_I!B zra=2_5^UKjFCsI*y2mOKbs*MbAL~wE;lmLw+F(J&#<`q9kzG$yHSJO5Cf1RPO?_X% z@`>0Gm}A6ptRo%eUa1)S3^lBLsyXF06yHQi+1(cSef1grbtU_rSgi55s`(DQhC}W9 z3;_Qe`FtmolS6g1?+Y(ZRCon;_=|pSux5bbNi>=*pO$am^j%m*wuPLD$t z<{s?#wN%&`F)$8T%sVrdu!mm3s_{AcfRas~R!=9l2N+K$E#c36+Fk;={s?#JMOsUR zedb((Khe#w*wrOx(c4=Zt7BC1 zrBveTB;c=}i(&oHBKnyVj7>dHw+K%sPfr-gfp0KVK3@dG+)=kI3nu0f*bU^$k`Vtc-brT!4HnfPW%v}c|lfAU!tLXyZ8_@_QQila}ojd1}1uplXi<|WG}Z7q#+K(r)&Q$i+{t%@O(s@ZX?cZ=QAjD6*O9!pwY+dj`? zcNo6%OQd z0+kNeNu~sD7#*@Qm^W$+_zsn6VW!B&*I_uBME2n~*e(B8-aDxV!snhIf1Y-!82cp- zUHFyaLP^oZ4GWB~dXWo)t3aDW!{)#XK=DrD08Y^lSb~{@L?k|W#dx1QtTV9^nd16= ze`GnF!$?>Jh0B|Uso``_9BU_g*#O_qNS}2jWH$t%QEgO6O;=PxlaJ1;Bc>HZV+k5i zr@ooURtr*SC;5jye1U zj8;M3sBBDGskwqGnY9vd@jpKc$!-hh)^wI(tf=Vq84}@= zYMzqv3;k4+mJi~JrIzyUkbII4_xWntLu2vEfBfsx+QX0R^|w<$W%8_^3~Aw7B%R`N zSjtbz{8@o-l`1$%ENuKB#)&oKW)}Hp`3X?v1YF3%0wf+@Av+TJgA>l`FkiDLx2I-j6#}v+p z3IGQO2ZMlsfc%){|4#oOJ1YoCG;~x9GAMF3Oj1!SN>+9X4o)%gKWhNk4+$_RaHSn4 z{IO>2Kr16B$#Iv1E-{~Pi=u$cBa1TJWTD!qtFAFy^!fhJuv%5P|c%SBHdnOi^ zf49Zdrmpssoqi|mlGV�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 Uw&uK=M=uajKgvQ60sj9t0k}&}NB{r; literal 0 HcmV?d00001 diff --git a/zukeUI/sources/zukebox.svg b/zukeUI/sources/zukebox.svg new file mode 100644 index 0000000..720a50c --- /dev/null +++ b/zukeUI/sources/zukebox.svg @@ -0,0 +1,3 @@ + + + diff --git a/zukeUI/tests/__init__.py b/zukeUI/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zukeUI/tests/test_zukedriver.py b/zukeUI/tests/test_zukedriver.py new file mode 100644 index 0000000..c38d133 --- /dev/null +++ b/zukeUI/tests/test_zukedriver.py @@ -0,0 +1,18 @@ +import unittest + +from utils.zukedriver import ZukeDriver + + +class ZukeDriverTest(unittest.TestCase): + def setUp(self): + self.zbd = ZukeDriver() + + def test_username(self): + self.assertEqual("MOHI 10.30.255.175 5000", self.zbd.getconf()) + + def test_sendcommand(self): + self.assertEqual("", self.zbd.getsendcommand()) + + +if __name__ == '__main__': + unittest.main() diff --git a/zukeUI/utils/__init__.py b/zukeUI/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zukeUI/utils/__pycache__/__init__.cpython-35.pyc b/zukeUI/utils/__pycache__/__init__.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c423ab6d4a4d6d13b52d11ce20ca45a519f1917 GIT binary patch literal 137 zcmWgR<>k7aI3t_^2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CUuOCl`MIh31*s*e zMY;JI`NjI4DXED8Mfq8&$tA`5Ri)Xfp`QAsC7C(J`tk9Zd6^~g@p=W7w>WHa^HWN5 MQtd$I6$3E?0BTSnQvd(} literal 0 HcmV?d00001 diff --git a/zukeUI/utils/__pycache__/zukedriver.cpython-35.pyc b/zukeUI/utils/__pycache__/zukedriver.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67fc8a7b2504bf06fdfa892006fa62560ad34167 GIT binary patch literal 3366 zcmcJSUvJ|?5WvTdI2 z0hLb;+fVRH_fRnWGav+Tb=!o{fXD>b1cRQ{Agn=Tfoth|3&J`?Hn=tz&sZIp4L_lp z4Hr13L38bO8h_%%(C-*>+0Bpnb`l>1hmjxqhg@K1yX=hlAWNvj&W3{fY(S&NpNWyb zU=K(03^En1!Z$FJ-s#qWTVx=!==Q<@x2}4|Mp2Iq42^0keQeADYpHe<*gP;twXw4e zY(ceKz!rfmsWvJs16xsT2iPjGHPuGtbzmE+y#QHTna_XrFPPiBtr4#hu#A_R` z^fMHhf{k{a0eA*vCN+)>II6*g@__o$C)MwvAAob$`o&6`-$5R_W;GvtOOEjYK} z%%VW-%UUi%Y2_&wg@ceX9%mnCrz2i0`J+)74E!ue;@(l3#M0V*vA16=^hTk7%0({| z{@^(6*wRu@>nx8(sUY8E1KGaBDMf?soRr7R6Z}=q)2ve$#6Yy^i9Pa#AEmM}#PH@K zl}*9ZQ4-@&0$g z4=FK>u{s2nLW(@qE+M4I4Vi1CxJBlOCz2$jbvUad(pF!&Cdf+Jh?fM+{cFiy8Z?Pk3rADLD$I|uQN@h#%J5VXIt@=1znb?XJSnm6b?m;hPm(Z?_)XAc zefP`#?XQZ(acEK?izMt=6>d{nmZIf4dUEf?4|852HUkSIFY~nO9pa-m?>h3CiMz4Z z{|Us(f}3%OchJr^gn?f2`r0uK3PlA3C6lnY*a$JqV#s=3L6R8oU3Y0{JK=7pWxWH z&U@psjG3=NMK*{JZ%Tb{wAXd%m2H~aON2M_sqRl}#Wg`HC^A1MAgbcx22vGws{-ZH zY?C0pbhe{ns8{rE_UTQ6E#)IaZbv>YoThKV~6@O;_JCoK+opQ|&Stdz}}u`AFk>Ov|R0+A)_5 H{MP;kz;Nmn literal 0 HcmV?d00001 diff --git a/zukeUI/utils/configuration/__init__.py b/zukeUI/utils/configuration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zukeUI/utils/configuration/__pycache__/__init__.cpython-35.pyc b/zukeUI/utils/configuration/__pycache__/__init__.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cd60e28b7cb56aa242b525d975e72500a5ac538 GIT binary patch literal 151 zcmWgR<>fljxHp^u2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CUoQF?`MIh31*s*e zMY;JI`NjI4DXED8Mfq8&$tA`5Ri)Xfp`QAsC7C(J`pNluX_@JzMTsSu`FZ;B@tJv< aCGqik1(mlrY;yBcN^?@}K&BT1F#`a)tS34E literal 0 HcmV?d00001 diff --git a/zukeUI/utils/configuration/__pycache__/zukeconfigmanager.cpython-35.pyc b/zukeUI/utils/configuration/__pycache__/zukeconfigmanager.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90f6a0c9d08452b94b0103fcf2d6dcfb1c7e5a19 GIT binary patch literal 1534 zcma)6!EVz)5S_K1#Bm)|K(x5(jRUeOH9vq*r5;cYM3t%vsMG>Mw9&X49l8ZD}{&@nm*(-t4?tZ}fV>kMFl zC;$?GH^Kqo!Z$RzaPC0DXzeieF3=H%8Og8_cvkm7 zv_Q12j!FlFZ}m2aE{MSDXj}skTHOcHL*z&1aWefV_p5T0A5YRUJ(emXXd3jUxsOwC zqBv!63cyhT7&ABrV3^Zk%!aXJ{83)WGMz}X!*9goJX11lB+o1oAE|1>X#;Nbv@Ax- z#rQu#wej`mZU3a2$bKywsV3D)rTYUR(*sqFW!C8a3^P6&^ruZ;=>9z5RHaQ`m3C=v zxjf8n{nhxK=Vjh-PQkT@0$7*Lwr*s(yKu}HIpLtJIyh|!@T}0_!(i;d*tLpBRiJXT zh*0DP_my2zU% zFQqom7Ll$MGImu9gC@b;N=zl|S30+QEm?CxHAyoS2G2y07JWIH;H zouMKV;{FG|g;57JIV_MjZ$%!|4+ibs3)(@@1MH7z1va`Bmb@McoGg2f`t$$CrVNhI zYx~{|$ZLo_jCYm3A)K3l^GPM91?eH@pQdRsUr|0rQ*@0LeU#L55_CqjMS?E*il8P@ pL>_I>A&Srmy|5d)^s_6mqt+K*D?4o<+HU+;^Gse->sbOr_bk7aI3t_^2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CUyk}2`MIh31*s*e zMY;JI`NjI4DXED8Mfq8&$tA`5Ri)Xfp`QAsC7C(J`pNluc|g(3{5<{m_{_Y_lK6PN Wg34PQHo5sJr8%i~Aft|oQv`=yCw+IFP7}NkuHrO64 zy}|Zj>BENt&pte0s1Nt}IiQ@&H28xAu*eCA6`*m7Th9>0Kud(k?-2(QB29^8NE5=t zmd{7N$WE92S8*oWR^&#i9F@C8Z`xsOBlw4aiO zG}AU~1m||lc_WLyCOhH$w$JMA7ey0P#{_hp_JBkvx(DRfRAS;Kf%72sg!(z#wo^?g d^f`j{q;?T|JdV~?Gk7aI3t_^2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CU#|KY`MIh31*s*e zMY;JI`NjI4DXED8Mfq8&$tA`5Ri)Xfp`QAsC7C(J`Xw1fsfj5WiFqkGnR)5@@$s2? bnI-Y@dIgoYIBatBQ%ZAE?LfvC12F>t#T_S3 literal 0 HcmV?d00001 diff --git a/zukeUI/utils/threadhandling/__pycache__/threaddecorators.cpython-35.pyc b/zukeUI/utils/threadhandling/__pycache__/threaddecorators.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eaf52e3df6c198787ccc23b771cd6727237b69a0 GIT binary patch literal 841 zcmb_ay-piJ5T3m|Uu-yM#^ToiH;_;uv7p$CgmIpd5d`r(DY9|My~IoHLq?Z7Ewa$_Q`Az;>dTOy zMm6M0>V>UC$C0xy5@eoXyOAi3(dJ^ZtSdjWbs<7`tv|ramvU+sx@@%9xUf^}%44PF zSG03oc~@R7zw2+u<b^_ z+Fwx|9U5CnM7u{9_8Fo&axuCDzP% bZqX1&x&cfmbPpF+EsZ|nM;;UO(|-3AMAx6B literal 0 HcmV?d00001 diff --git a/zukeUI/utils/threadhandling/threaddecorators.py b/zukeUI/utils/threadhandling/threaddecorators.py new file mode 100644 index 0000000..0e5f764 --- /dev/null +++ b/zukeUI/utils/threadhandling/threaddecorators.py @@ -0,0 +1,17 @@ +import threading + + +def daemon(function): + def wrapped_func(*args): + parallel_function = threading.Thread(target=function,args=args) + parallel_function.daemon = True + parallel_function.start() + + return wrapped_func + +def thread(function): + def wrapped_func(*args): + parallel_function = threading.Thread(target=function,args=args) + parallel_function.start() + + return wrapped_func diff --git a/zukeUI/utils/zukedriver.py b/zukeUI/utils/zukedriver.py new file mode 100644 index 0000000..ce61f09 --- /dev/null +++ b/zukeUI/utils/zukedriver.py @@ -0,0 +1,72 @@ +import json + +from utils.configuration.zukeconfigmanager import ZukeConfigmanager +from utils.connection.zukeconnector import ZukeConnector +from utils.threadhandling.threaddecorators import thread + + +class ZukeDriver: + def __init__(self): + self.zuke_configurator = ZukeConfigmanager("sources/config.txt") + self.zuke_connector = ZukeConnector(self.zuke_configurator.get_config()) + self.follow = True + + @thread + def send_track(self, url): + params = json.dumps({'url': url, 'user': self.zuke_configurator.getUser()}) + headers = {'content-type': 'application/json'} + response = self.zuke_connector.send_request("POST", "/player/tracks", params, headers) + return response + + @thread + def send_track_with_message(self, url, message): + params = json.dumps({'url': url, 'user': self.zuke_configurator.getUser(), 'message': message, 'lang': 'hu'}) + headers = {'content-type': 'application/json'} + response = self.zuke_connector.send_request("POST", "/player/tracks", params, headers) + return response + + def set_volume(self, volume_value): + params = json.dumps({'volume': volume_value}) + headers = {'content-type': 'application/json'} + self.zuke_connector.send_request("PATCH", "/player/control", params, headers) + + def set_seek(self, seek_value): + params = json.dumps({'time': seek_value}) + headers = {'content-type': 'application/json'} + self.zuke_connector.send_request("PATCH", "/player/control", params, headers) + + def get_control(self): + json_response = self.zuke_connector.send_request("GET", "/player/control") + return json_response + + def get_tracks(self): + json_response = self.zuke_connector.send_request("GET", "/player/tracks") + return json_response + + def get_recent_tracks(self): + json_response=self.zuke_connector.send_request("GET", "/player/recent-tracks") + return json_response + + def delete_track(self, track_id): + json_response = self.zuke_connector.send_request("DELETE", "/player/tracks/{number}".format(number=track_id)) + return json_response + + def play_or_pause(self, play_or_pause): + params = json.dumps({'playing': play_or_pause}) + headers = {'content-type': 'application/json'} + json_response =self.zuke_connector.send_request("PATCH", "/player/control", params, headers) + return json_response + + def getvolume(self): + return self.get_control()["volume"] + + # def getconf(self): + # return self.username + " " + self.zukebox_ip + " " + self.zukebox_port + # + # def getsendcommand(self): + # return "connection POST {ip}:{port}/player/tracks url=\"{url}\" user=\"{user}\"".format( + # ip=self.zukebox_ip, + # port=self.zukebox_port, + # url="x", + # user=self.username + # ) From 2a5897c6684c75b5b9d6fceb1f944274f066c9b2 Mon Sep 17 00:00:00 2001 From: Peter Mohos Date: Thu, 22 Dec 2016 15:12:20 +0100 Subject: [PATCH 2/4] mohi: mukodo --- zukeUI/assets/config.json | 5 + zukeUI/{sources => assets}/default.jpg | Bin zukeUI/assets/zukeUI.ui | 242 ++++++++++++++++++ zukeUI/{sources => assets}/zukebox.jpg | Bin zukeUI/{sources => assets}/zukebox.svg | 0 zukeUI/{tests => driver}/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 0 -> 133 bytes zukeUI/driver/driver.iml | 6 + zukeUI/{ => driver}/utils/__init__.py | 0 .../utils/__pycache__/__init__.cpython-35.pyc | Bin 0 -> 138 bytes .../utils/configuration/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 0 -> 152 bytes .../zukeconfiguration.cpython-35.pyc | Bin 0 -> 3239 bytes .../utils/configuration/zukeconfiguration.py | 74 ++++++ .../{ => driver}/utils/connection/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 0 -> 149 bytes .../request_managing.cpython-35.pyc | Bin 0 -> 6460 bytes .../__pycache__/zukeconnection.cpython-35.pyc | Bin 0 -> 2320 bytes .../zukerequest_managing.cpython-35.pyc | Bin 0 -> 3326 bytes .../utils/connection/request_managing.py | 175 +++++++++++++ .../driver/utils/connection/zukeconnection.py | 56 ++++ zukeUI/run_zukeui.py | 170 ------------ zukeUI/runner.sh | 3 + zukeUI/sources/config.txt | 3 - zukeUI/tests/test_zukedriver.py | 18 -- .../{utils/threadhandling => ui}/__init__.py | 0 zukeUI/ui/__pycache__/__init__.cpython-35.pyc | Bin 0 -> 129 bytes zukeUI/ui/zukeui.py | 130 ++++++++++ .../utils/__pycache__/__init__.cpython-35.pyc | Bin 137 -> 0 bytes .../__pycache__/zukedriver.cpython-35.pyc | Bin 3366 -> 0 bytes .../__pycache__/__init__.cpython-35.pyc | Bin 151 -> 0 bytes .../zukeconfigmanager.cpython-35.pyc | Bin 1534 -> 0 bytes .../utils/configuration/zukeconfigmanager.py | 29 --- .../__pycache__/__init__.cpython-35.pyc | Bin 148 -> 0 bytes .../__pycache__/zukeconnector.cpython-35.pyc | Bin 999 -> 0 bytes zukeUI/utils/connection/zukeconnector.py | 20 -- .../__pycache__/__init__.cpython-35.pyc | Bin 152 -> 0 bytes .../threaddecorators.cpython-35.pyc | Bin 841 -> 0 bytes .../utils/threadhandling/threaddecorators.py | 17 -- zukeUI/utils/zukedriver.py | 72 ------ zukeUI/zukeUI.iml | 13 + 41 files changed, 704 insertions(+), 329 deletions(-) create mode 100644 zukeUI/assets/config.json rename zukeUI/{sources => assets}/default.jpg (100%) create mode 100644 zukeUI/assets/zukeUI.ui rename zukeUI/{sources => assets}/zukebox.jpg (100%) rename zukeUI/{sources => assets}/zukebox.svg (100%) rename zukeUI/{tests => driver}/__init__.py (100%) create mode 100644 zukeUI/driver/__pycache__/__init__.cpython-35.pyc create mode 100644 zukeUI/driver/driver.iml rename zukeUI/{ => driver}/utils/__init__.py (100%) create mode 100644 zukeUI/driver/utils/__pycache__/__init__.cpython-35.pyc rename zukeUI/{ => driver}/utils/configuration/__init__.py (100%) create mode 100644 zukeUI/driver/utils/configuration/__pycache__/__init__.cpython-35.pyc create mode 100644 zukeUI/driver/utils/configuration/__pycache__/zukeconfiguration.cpython-35.pyc create mode 100644 zukeUI/driver/utils/configuration/zukeconfiguration.py rename zukeUI/{ => driver}/utils/connection/__init__.py (100%) create mode 100644 zukeUI/driver/utils/connection/__pycache__/__init__.cpython-35.pyc create mode 100644 zukeUI/driver/utils/connection/__pycache__/request_managing.cpython-35.pyc create mode 100644 zukeUI/driver/utils/connection/__pycache__/zukeconnection.cpython-35.pyc create mode 100644 zukeUI/driver/utils/connection/__pycache__/zukerequest_managing.cpython-35.pyc create mode 100644 zukeUI/driver/utils/connection/request_managing.py create mode 100644 zukeUI/driver/utils/connection/zukeconnection.py delete mode 100644 zukeUI/run_zukeui.py create mode 100755 zukeUI/runner.sh delete mode 100644 zukeUI/sources/config.txt delete mode 100644 zukeUI/tests/test_zukedriver.py rename zukeUI/{utils/threadhandling => ui}/__init__.py (100%) create mode 100644 zukeUI/ui/__pycache__/__init__.cpython-35.pyc create mode 100644 zukeUI/ui/zukeui.py delete mode 100644 zukeUI/utils/__pycache__/__init__.cpython-35.pyc delete mode 100644 zukeUI/utils/__pycache__/zukedriver.cpython-35.pyc delete mode 100644 zukeUI/utils/configuration/__pycache__/__init__.cpython-35.pyc delete mode 100644 zukeUI/utils/configuration/__pycache__/zukeconfigmanager.cpython-35.pyc delete mode 100644 zukeUI/utils/configuration/zukeconfigmanager.py delete mode 100644 zukeUI/utils/connection/__pycache__/__init__.cpython-35.pyc delete mode 100644 zukeUI/utils/connection/__pycache__/zukeconnector.cpython-35.pyc delete mode 100644 zukeUI/utils/connection/zukeconnector.py delete mode 100644 zukeUI/utils/threadhandling/__pycache__/__init__.cpython-35.pyc delete mode 100644 zukeUI/utils/threadhandling/__pycache__/threaddecorators.cpython-35.pyc delete mode 100644 zukeUI/utils/threadhandling/threaddecorators.py delete mode 100644 zukeUI/utils/zukedriver.py create mode 100644 zukeUI/zukeUI.iml 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/sources/default.jpg b/zukeUI/assets/default.jpg similarity index 100% rename from zukeUI/sources/default.jpg rename to zukeUI/assets/default.jpg diff --git a/zukeUI/assets/zukeUI.ui b/zukeUI/assets/zukeUI.ui new file mode 100644 index 0000000..a71332c --- /dev/null +++ b/zukeUI/assets/zukeUI.ui @@ -0,0 +1,242 @@ + + + Form + + + + 0 + 0 + 569 + 708 + + + + 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 + + + + + + + + + + 50 + 410 + 461 + 20 + + + + + + + Qt::Horizontal + + + + + + 50 + 440 + 461 + 20 + + + + + + + 100 + + + Qt::Horizontal + + + + + + 50 + 380 + 473 + 21 + + + + + DejaVu Sans + 12 + 50 + false + false + + + + + + + Qt::AlignCenter + + + true + + + + + + 60 + 670 + 441 + 31 + + + + + 12 + 75 + true + + + + + + + Qt::AlignCenter + + + frame_2 + widget + track_title + volume_slider + seek_slider + track_sender + error_message + + + + diff --git a/zukeUI/sources/zukebox.jpg b/zukeUI/assets/zukebox.jpg similarity index 100% rename from zukeUI/sources/zukebox.jpg rename to zukeUI/assets/zukebox.jpg diff --git a/zukeUI/sources/zukebox.svg b/zukeUI/assets/zukebox.svg similarity index 100% rename from zukeUI/sources/zukebox.svg rename to zukeUI/assets/zukebox.svg diff --git a/zukeUI/tests/__init__.py b/zukeUI/driver/__init__.py similarity index 100% rename from zukeUI/tests/__init__.py rename to zukeUI/driver/__init__.py diff --git a/zukeUI/driver/__pycache__/__init__.cpython-35.pyc b/zukeUI/driver/__pycache__/__init__.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9ef8261710d618400f1c4c65551fd88cdeed8d6 GIT binary patch literal 133 zcmWgR<>kuJ4UAv_g2x~N1{i@12OutH0TL+;48fX=ek&P@K*9*(myv!(er~FML25~A zQEq-lezAU4X?AK-eg&8g_0&%($}CGQ(vOeN%*!l^kJl@xyv1RYo1apelWGSts~CtG E0B^1z3;+NC literal 0 HcmV?d00001 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/utils/__init__.py b/zukeUI/driver/utils/__init__.py similarity index 100% rename from zukeUI/utils/__init__.py rename to zukeUI/driver/utils/__init__.py 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 0000000000000000000000000000000000000000..5915eed6494913597d7c54c9fa025921d7142046 GIT binary patch literal 138 zcmWgR<>hKI4UAv_g2x~N1{i@12OutH0TL+;48fX=ek&P@K*9*(m$`mMer~FML25~A zQEq-lezAU4X?AK-eg&8g_0&%($}CGQ(l0H^%+ZgJ&&fljxHp^u2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CU#|KY`MIh31*s*e zMY;JI`NjHGrP--T`4wO~)KfpDD6=fJNWZisGe5boJs{aT9cI5znsQK&$91K5WY#f73w1(PyVv(NRD`r=& zSV=CJ8y6mc7vR7H@ERQF%86It#MiwmY5kYNXf?AlJ-z)i{dLc*)a%t>e{VhevqKuB`UUcfLM=|H z1yD=mmxWrMP>bYOs116R+GX*?bYkNi0&Du5HSmH|X>O&ofoe>qq{m$K#sk{V?kE(v{yxmmpcXf40|mqkgbH z43a?iqunT8A4B^0(F=J$-CFl`co69IQ4;pz^{!|g=}r1^&;FkcR^B3^e$_giXI#3^ice$G;%s0tJ9C z{w1CsAwNn&0gfvZ3V;rVDkD4NsR(6)aQ><)lnKC@GDiS+%Uv{!v-mvf3_8yP4P4Kq zaPWqmWVg!z;2*!aJwS_B(7jUhipV1hF~&gf-Evjawajhrlw8ltNj2BcXTax99Q1Z{ z4N7vjU`TLcgo7~gytmj4-dIu!$Fxm1W^Sr2QFJX0r)b31x1WBs>1{ne z+gDQ>qvRkh-pMJycG};J>!RALsrJX)Prg1!e?=s7D!!Mq;Kn%?FyH^Q;c090$s29p z0&wp?*dT=>56@}L8CPk{fzYEY@|Io#**N>bVSJAMN#-)kTF!zqaF=Bxb2i=DiE?c0 zIMJFyJTC~54)(ZC(wWe}g> z;?+QI4ULyvs9zh0UvAYn{E=~bQO7q9!Zt8Vvn2S259Ll0X>&!+=S~uMY(rL^HSd)i zyr#6ZEvKcN{ic{&m2GhfgENZVNBCTBJY?DGs;a9p+5+16&di2fIn9^9rdv7*!&;o+ zlG5!5$!_H9n-FdsKkO!g$}=IyIJnP7@Yzs<5rT|kK;uit2D7}6?ljP5D3cQz@oNKm zDa!8R3#1i*IV%9XU!S4Pq9Hq2*LOfPRul`~Ol9*$ zHyHA5RKOQGt))x< E0%@&=g8%>k literal 0 HcmV?d00001 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/utils/connection/__init__.py b/zukeUI/driver/utils/connection/__init__.py similarity index 100% rename from zukeUI/utils/connection/__init__.py rename to zukeUI/driver/utils/connection/__init__.py 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 0000000000000000000000000000000000000000..a431554f9eb11fe38d9409a7ceb48c39e49d1f57 GIT binary patch literal 149 zcmWgR<>k7aI3t_^2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CUrzcN`MIh31*s*e zMY;JI`NjHGrP--T`4wO~)KfpDD6=fJNWZisGePO2Tq>|!8h002`FCQ$$Y literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e172e8b3049beb1437bb101f982ff4773223e0a6 GIT binary patch literal 6460 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 XKcgxYbX2!R8*95_Zd#kx+Ul5TE<>~r?+ zs>TWrDE$C@4J1C%KJe0~eg&TR&8(fqPFh|7arW)a?#}Ga{B~w`E;XC=;E$(2y=oA> zq7#n~`X1hV4^4us6Lfwk(`;-PWs8Y8|gBo>f z(^1^aVI-5IM#YvBsnLYuCdwBET(J0s;0?CI(2qbt?8DE>T=p-i_HBfnui0Fv`WLULD zykgWZAmHCH3NFs(h(&!^=hLA_`U^Vr*+1a@=(3wu2(#iVVD+c~pB1Z23B&>Z5(Gt+ zc5A!@1Z+Sc2>JlacP)sV5jMicn(<1V8o$hy))%3Z@zWSST(F8>SfI2 zSJ04i#R;6Ncs+4N4^+!(xZ|s59iYe_J0d&b@tLioV=*rzTyZ=&2p$1}6Pl*0qC&fp zlW@9>ImV|M>+})S)=Xvn{^shVachb+L#ne>3^c*>EG-90U&H2qi+1%5s)D<|0V#cx zn_Jwxj|M@)0nu&Pg`?+jS}F_cX)ue(a(x7eoS~-~7?;n6aVqtn!l*eHoK z!rRo|I{>WZs)(59vvx~5VR7~M|LFO%g3>q6=|7M z@xDhtN+R`^Ug?uoI~Cko4BE;*O9&hMpMaCrADD+)7~s1#?$3+4P29O zY@7^8x9*P5&mjW#v+EFs)~2)kw%o3$J7BM^MC=qU9DpYlx@()r9Ad{^t78%CB6LMZ zQ#&?bPTO|QY$wW*r>wfI47e^pWpHy&;dzOfQ#`JsZXIi{ug@xtjQd9E2hfm}HvWFu zFWv%aD=)Iq2_0kBk^P0NhBr=WIKv*cxE3461BF_w9z}yJDO08!QM6yish!at)|28$ zZawh^0cAMnI&ZaB?~%j*@UKH=t5Zr-3O6qhEN739lR9HHSZ2UjZB+N~!48qHQ9 z^WmaR{MN1351tgC+bZ&|t?E{!s#~$;W2xohUg7I_W<*pscjiSVh2WHb(zVt}9mdBj bYMiCNseNgI`if&DXGkuJ4UAv_g2x~N1{i@12OutH0TL+;48fX=ek&P@K*9*(m!5t`er~FML25~A zQEq-lezAU4X?AK-eg&8g_0%uT)Q^wP%*!l^kJl@xyv1RYo1apelWGStrx=JC0OT|s AS^xk5 literal 0 HcmV?d00001 diff --git a/zukeUI/ui/zukeui.py b/zukeUI/ui/zukeui.py new file mode 100644 index 0000000..d22b65b --- /dev/null +++ b/zukeUI/ui/zukeui.py @@ -0,0 +1,130 @@ +import os +import sys + +import time +import urllib.request + +from PyQt5 import QtWidgets + +from PyQt5 import uic +from PyQt5.QtGui import QMouseEvent +from PyQt5.QtGui import QPixmap +from PyQt5.QtWidgets import QApplication + +from driver.utils.configuration.zukeconfiguration import ZukeConfigManager +from driver.utils.connection.zukeconnection import ZukeAvailabilityError, InvalidZukeResponseError +from driver.utils.connection.request_managing import UrlSendingManager, ZukeUiManager, VolumeSetterManager, SeekSetterManager, RefreshManager, \ + PlayPauseManager + + +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: + self.__zuc.play_or_pause(0 if self.__zuc.control["playing"] else 1) + 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 + + +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( + UrlSendingManager(zcm), + VolumeSetterManager(zcm), + SeekSetterManager(zcm), + PlayPauseManager(zcm), + RefreshManager(zcm) + ) + ) + w.show() + sys.exit(app.exec_()) diff --git a/zukeUI/utils/__pycache__/__init__.cpython-35.pyc b/zukeUI/utils/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 7c423ab6d4a4d6d13b52d11ce20ca45a519f1917..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137 zcmWgR<>k7aI3t_^2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CUuOCl`MIh31*s*e zMY;JI`NjI4DXED8Mfq8&$tA`5Ri)Xfp`QAsC7C(J`tk9Zd6^~g@p=W7w>WHa^HWN5 MQtd$I6$3E?0BTSnQvd(} diff --git a/zukeUI/utils/__pycache__/zukedriver.cpython-35.pyc b/zukeUI/utils/__pycache__/zukedriver.cpython-35.pyc deleted file mode 100644 index 67fc8a7b2504bf06fdfa892006fa62560ad34167..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3366 zcmcJSUvJ|?5WvTdI2 z0hLb;+fVRH_fRnWGav+Tb=!o{fXD>b1cRQ{Agn=Tfoth|3&J`?Hn=tz&sZIp4L_lp z4Hr13L38bO8h_%%(C-*>+0Bpnb`l>1hmjxqhg@K1yX=hlAWNvj&W3{fY(S&NpNWyb zU=K(03^En1!Z$FJ-s#qWTVx=!==Q<@x2}4|Mp2Iq42^0keQeADYpHe<*gP;twXw4e zY(ceKz!rfmsWvJs16xsT2iPjGHPuGtbzmE+y#QHTna_XrFPPiBtr4#hu#A_R` z^fMHhf{k{a0eA*vCN+)>II6*g@__o$C)MwvAAob$`o&6`-$5R_W;GvtOOEjYK} z%%VW-%UUi%Y2_&wg@ceX9%mnCrz2i0`J+)74E!ue;@(l3#M0V*vA16=^hTk7%0({| z{@^(6*wRu@>nx8(sUY8E1KGaBDMf?soRr7R6Z}=q)2ve$#6Yy^i9Pa#AEmM}#PH@K zl}*9ZQ4-@&0$g z4=FK>u{s2nLW(@qE+M4I4Vi1CxJBlOCz2$jbvUad(pF!&Cdf+Jh?fM+{cFiy8Z?Pk3rADLD$I|uQN@h#%J5VXIt@=1znb?XJSnm6b?m;hPm(Z?_)XAc zefP`#?XQZ(acEK?izMt=6>d{nmZIf4dUEf?4|852HUkSIFY~nO9pa-m?>h3CiMz4Z z{|Us(f}3%OchJr^gn?f2`r0uK3PlA3C6lnY*a$JqV#s=3L6R8oU3Y0{JK=7pWxWH z&U@psjG3=NMK*{JZ%Tb{wAXd%m2H~aON2M_sqRl}#Wg`HC^A1MAgbcx22vGws{-ZH zY?C0pbhe{ns8{rE_UTQ6E#)IaZbv>YoThKV~6@O;_JCoK+opQ|&Stdz}}u`AFk>Ov|R0+A)_5 H{MP;kz;Nmn diff --git a/zukeUI/utils/configuration/__pycache__/__init__.cpython-35.pyc b/zukeUI/utils/configuration/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 7cd60e28b7cb56aa242b525d975e72500a5ac538..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmWgR<>fljxHp^u2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CUoQF?`MIh31*s*e zMY;JI`NjI4DXED8Mfq8&$tA`5Ri)Xfp`QAsC7C(J`pNluX_@JzMTsSu`FZ;B@tJv< aCGqik1(mlrY;yBcN^?@}K&BT1F#`a)tS34E diff --git a/zukeUI/utils/configuration/__pycache__/zukeconfigmanager.cpython-35.pyc b/zukeUI/utils/configuration/__pycache__/zukeconfigmanager.cpython-35.pyc deleted file mode 100644 index 90f6a0c9d08452b94b0103fcf2d6dcfb1c7e5a19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1534 zcma)6!EVz)5S_K1#Bm)|K(x5(jRUeOH9vq*r5;cYM3t%vsMG>Mw9&X49l8ZD}{&@nm*(-t4?tZ}fV>kMFl zC;$?GH^Kqo!Z$RzaPC0DXzeieF3=H%8Og8_cvkm7 zv_Q12j!FlFZ}m2aE{MSDXj}skTHOcHL*z&1aWefV_p5T0A5YRUJ(emXXd3jUxsOwC zqBv!63cyhT7&ABrV3^Zk%!aXJ{83)WGMz}X!*9goJX11lB+o1oAE|1>X#;Nbv@Ax- z#rQu#wej`mZU3a2$bKywsV3D)rTYUR(*sqFW!C8a3^P6&^ruZ;=>9z5RHaQ`m3C=v zxjf8n{nhxK=Vjh-PQkT@0$7*Lwr*s(yKu}HIpLtJIyh|!@T}0_!(i;d*tLpBRiJXT zh*0DP_my2zU% zFQqom7Ll$MGImu9gC@b;N=zl|S30+QEm?CxHAyoS2G2y07JWIH;H zouMKV;{FG|g;57JIV_MjZ$%!|4+ibs3)(@@1MH7z1va`Bmb@McoGg2f`t$$CrVNhI zYx~{|$ZLo_jCYm3A)K3l^GPM91?eH@pQdRsUr|0rQ*@0LeU#L55_CqjMS?E*il8P@ pL>_I>A&Srmy|5d)^s_6mqt+K*D?4o<+HU+;^Gse->sbOr_bk7aI3t_^2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CUyk}2`MIh31*s*e zMY;JI`NjI4DXED8Mfq8&$tA`5Ri)Xfp`QAsC7C(J`pNluc|g(3{5<{m_{_Y_lK6PN Wg34PQHo5sJr8%i~Aft|oQv`=yCw+IFP7}NkuHrO64 zy}|Zj>BENt&pte0s1Nt}IiQ@&H28xAu*eCA6`*m7Th9>0Kud(k?-2(QB29^8NE5=t zmd{7N$WE92S8*oWR^&#i9F@C8Z`xsOBlw4aiO zG}AU~1m||lc_WLyCOhH$w$JMA7ey0P#{_hp_JBkvx(DRfRAS;Kf%72sg!(z#wo^?g d^f`j{q;?T|JdV~?Gk7aI3t_^2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CU#|KY`MIh31*s*e zMY;JI`NjI4DXED8Mfq8&$tA`5Ri)Xfp`QAsC7C(J`Xw1fsfj5WiFqkGnR)5@@$s2? bnI-Y@dIgoYIBatBQ%ZAE?LfvC12F>t#T_S3 diff --git a/zukeUI/utils/threadhandling/__pycache__/threaddecorators.cpython-35.pyc b/zukeUI/utils/threadhandling/__pycache__/threaddecorators.cpython-35.pyc deleted file mode 100644 index eaf52e3df6c198787ccc23b771cd6727237b69a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 841 zcmb_ay-piJ5T3m|Uu-yM#^ToiH;_;uv7p$CgmIpd5d`r(DY9|My~IoHLq?Z7Ewa$_Q`Az;>dTOy zMm6M0>V>UC$C0xy5@eoXyOAi3(dJ^ZtSdjWbs<7`tv|ramvU+sx@@%9xUf^}%44PF zSG03oc~@R7zw2+u<b^_ z+Fwx|9U5CnM7u{9_8Fo&axuCDzP% bZqX1&x&cfmbPpF+EsZ|nM;;UO(|-3AMAx6B diff --git a/zukeUI/utils/threadhandling/threaddecorators.py b/zukeUI/utils/threadhandling/threaddecorators.py deleted file mode 100644 index 0e5f764..0000000 --- a/zukeUI/utils/threadhandling/threaddecorators.py +++ /dev/null @@ -1,17 +0,0 @@ -import threading - - -def daemon(function): - def wrapped_func(*args): - parallel_function = threading.Thread(target=function,args=args) - parallel_function.daemon = True - parallel_function.start() - - return wrapped_func - -def thread(function): - def wrapped_func(*args): - parallel_function = threading.Thread(target=function,args=args) - parallel_function.start() - - return wrapped_func diff --git a/zukeUI/utils/zukedriver.py b/zukeUI/utils/zukedriver.py deleted file mode 100644 index ce61f09..0000000 --- a/zukeUI/utils/zukedriver.py +++ /dev/null @@ -1,72 +0,0 @@ -import json - -from utils.configuration.zukeconfigmanager import ZukeConfigmanager -from utils.connection.zukeconnector import ZukeConnector -from utils.threadhandling.threaddecorators import thread - - -class ZukeDriver: - def __init__(self): - self.zuke_configurator = ZukeConfigmanager("sources/config.txt") - self.zuke_connector = ZukeConnector(self.zuke_configurator.get_config()) - self.follow = True - - @thread - def send_track(self, url): - params = json.dumps({'url': url, 'user': self.zuke_configurator.getUser()}) - headers = {'content-type': 'application/json'} - response = self.zuke_connector.send_request("POST", "/player/tracks", params, headers) - return response - - @thread - def send_track_with_message(self, url, message): - params = json.dumps({'url': url, 'user': self.zuke_configurator.getUser(), 'message': message, 'lang': 'hu'}) - headers = {'content-type': 'application/json'} - response = self.zuke_connector.send_request("POST", "/player/tracks", params, headers) - return response - - def set_volume(self, volume_value): - params = json.dumps({'volume': volume_value}) - headers = {'content-type': 'application/json'} - self.zuke_connector.send_request("PATCH", "/player/control", params, headers) - - def set_seek(self, seek_value): - params = json.dumps({'time': seek_value}) - headers = {'content-type': 'application/json'} - self.zuke_connector.send_request("PATCH", "/player/control", params, headers) - - def get_control(self): - json_response = self.zuke_connector.send_request("GET", "/player/control") - return json_response - - def get_tracks(self): - json_response = self.zuke_connector.send_request("GET", "/player/tracks") - return json_response - - def get_recent_tracks(self): - json_response=self.zuke_connector.send_request("GET", "/player/recent-tracks") - return json_response - - def delete_track(self, track_id): - json_response = self.zuke_connector.send_request("DELETE", "/player/tracks/{number}".format(number=track_id)) - return json_response - - def play_or_pause(self, play_or_pause): - params = json.dumps({'playing': play_or_pause}) - headers = {'content-type': 'application/json'} - json_response =self.zuke_connector.send_request("PATCH", "/player/control", params, headers) - return json_response - - def getvolume(self): - return self.get_control()["volume"] - - # def getconf(self): - # return self.username + " " + self.zukebox_ip + " " + self.zukebox_port - # - # def getsendcommand(self): - # return "connection POST {ip}:{port}/player/tracks url=\"{url}\" user=\"{user}\"".format( - # ip=self.zukebox_ip, - # port=self.zukebox_port, - # url="x", - # user=self.username - # ) 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 From a52c69711fea38d42ae5cb9235752e1262962ee0 Mon Sep 17 00:00:00 2001 From: Peter Mohos Date: Thu, 29 Jun 2017 15:50:35 +0200 Subject: [PATCH 3/4] mohi: megy2 --- zukeUI/assets/zukeUI.ui | 75 +++++++++++++-- .../utils/connection/request_managing.py | 93 ++++--------------- zukeUI/runner.sh | 7 +- zukeUI/ui/zukeui.py | 21 +++-- zukeUI/ui/zukeui_manager.py | 33 +++++++ 5 files changed, 137 insertions(+), 92 deletions(-) create mode 100644 zukeUI/ui/zukeui_manager.py diff --git a/zukeUI/assets/zukeUI.ui b/zukeUI/assets/zukeUI.ui index a71332c..d2fa9f7 100644 --- a/zukeUI/assets/zukeUI.ui +++ b/zukeUI/assets/zukeUI.ui @@ -2,6 +2,9 @@ Form + + Qt::ApplicationModal + 0 @@ -10,6 +13,9 @@ 708 + + Qt::DefaultContextMenu + ZukeUI @@ -146,15 +152,18 @@ - 50 + 130 410 - 461 + 381 20 + + false + Qt::Horizontal @@ -162,9 +171,9 @@ - 50 + 130 440 - 461 + 381 20 @@ -174,6 +183,9 @@ 100 + + false + Qt::Horizontal @@ -181,9 +193,9 @@ - 50 + 142 380 - 473 + 381 21 @@ -229,6 +241,54 @@ 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 @@ -236,6 +296,9 @@ seek_slider track_sender error_message + volume_label + label_2 + sentby_label diff --git a/zukeUI/driver/utils/connection/request_managing.py b/zukeUI/driver/utils/connection/request_managing.py index c1dc4d9..0a75e1c 100644 --- a/zukeUI/driver/utils/connection/request_managing.py +++ b/zukeUI/driver/utils/connection/request_managing.py @@ -13,18 +13,15 @@ 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 run(self): - pass - - -class UrlSendingManager(RequestManager): - def send_track(self, url, response_callback=None, message: str=None): body = {'url': url, 'user': self._config_manager.zuke_user} @@ -42,9 +39,6 @@ def send_track(self, url, response_callback=None, message: str=None): response_callback ) - -class VolumeSetterManager(RequestManager): - def set_volume(self, volume_value, response_callback=None): self.send_request( ZukeRequest( @@ -58,9 +52,6 @@ def set_volume(self, volume_value, response_callback=None): response_callback ) - -class SeekSetterManager(RequestManager): - def set_seek(self, seek_value, response_callback=None): self.send_request( ZukeRequest( @@ -75,8 +66,6 @@ def set_seek(self, seek_value, response_callback=None): ) -class PlayPauseManager(RequestManager): - manager_id = "playpause_manager" response_signal = stop_signal = QtCore.pyqtSignal(dict, name=manager_id) @@ -93,33 +82,25 @@ def play_or_pause(self, play_or_pause, response_callback=None): response_callback ) - -class RefreshManager(RequestManager): - - manager_id = "refresh_manager" - response_signal = stop_signal = QtCore.pyqtSignal(dict, name=manager_id) - - def __init__(self, zcm: ZukeConfigManager): - super().__init__(zcm) - self.__control = None - self._refreshing = False + 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.send_request( - ZukeRequest( - self._config_manager.zuke_ip, - self._config_manager.zuke_port, - "GET", - "/player/control" - ), - self._callback - ) + self.get_control(self._callback) time.sleep(1) - @property - def control(self): - return self.__control + def _refresh_callback(self, response): + self._control = response + self._callback(response) def start_refreshing(self, callback=None): if not self._refreshing: @@ -132,44 +113,6 @@ def stop_refreshing(self): self._refreshing = False self.quit() - -class ZukeUiManager: - - def __init__( - self, - usm: UrlSendingManager, - vsm: VolumeSetterManager, - ssm: SeekSetterManager, - ppm: PlayPauseManager, - rm: RefreshManager - ): - self.prev_control = None - self.__usm = usm - self.__vsm = vsm - self.__ssm = ssm - self.__ppm = ppm - self.__rm = rm - - def send_url(self, url: str, message: str, callback=None): - if url: - self.__usm.send_track(url, callback, message) - - def set_volume(self, volume, callback=None): - self.__vsm.set_volume(volume, callback) - - def set_seek(self, seek, callback=None): - self.__ssm.set_seek(seek, callback) - - def play_or_pause(self, play_or_pause, callback=None): - self.__ppm.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) - @property def control(self): - return self.__rm.control - + return self._control \ No newline at end of file diff --git a/zukeUI/runner.sh b/zukeUI/runner.sh index c5e3048..64b117a 100755 --- a/zukeUI/runner.sh +++ b/zukeUI/runner.sh @@ -1,3 +1,8 @@ #!/bin/bash -python3 ./ui/zukeui.py ./assets/ \ No newline at end of file +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/zukeui.py b/zukeUI/ui/zukeui.py index d22b65b..4e90ec8 100644 --- a/zukeUI/ui/zukeui.py +++ b/zukeUI/ui/zukeui.py @@ -1,20 +1,19 @@ import os import sys -import time import urllib.request from PyQt5 import QtWidgets from PyQt5 import uic -from PyQt5.QtGui import QMouseEvent 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 UrlSendingManager, ZukeUiManager, VolumeSetterManager, SeekSetterManager, RefreshManager, \ - PlayPauseManager +from driver.utils.connection.request_managing import RequestManager +from zukeui_manager import ZukeUiManager class Ui(QtWidgets.QDialog): @@ -67,7 +66,10 @@ def _send_callback(self, response): def play_or_pause(self): try: - self.__zuc.play_or_pause(0 if self.__zuc.control["playing"] else 1) + 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") @@ -110,6 +112,9 @@ def _refresh_callback(self, control: dict): 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] @@ -119,11 +124,7 @@ def _refresh_callback(self, control: dict): w = Ui( os.path.join(assets_path, 'zukeUI.ui'), ZukeUiManager( - UrlSendingManager(zcm), - VolumeSetterManager(zcm), - SeekSetterManager(zcm), - PlayPauseManager(zcm), - RefreshManager(zcm) + RequestManager(zcm) ) ) w.show() 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 From 2980163eae26157f8b8858761cf8cde3af786a14 Mon Sep 17 00:00:00 2001 From: Peter Mohos Date: Thu, 29 Jun 2017 16:00:18 +0200 Subject: [PATCH 4/4] idea: removed --- zukeUI/.idea/workspace.xml | 810 ------------------------------------- 1 file changed, 810 deletions(-) delete mode 100644 zukeUI/.idea/workspace.xml diff --git a/zukeUI/.idea/workspace.xml b/zukeUI/.idea/workspace.xml deleted file mode 100644 index 3db62cd..0000000 --- a/zukeUI/.idea/workspace.xml +++ /dev/null @@ -1,810 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1472036699249 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file