Initial working Windows Service.
This commit is contained in:
parent
f64840702f
commit
65226e09dc
9 changed files with 293 additions and 119 deletions
98
install/SMWinservice.py
Normal file
98
install/SMWinservice.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
'''
|
||||
SMWinservice
|
||||
by Davide Mastromatteo
|
||||
|
||||
Base class to create winservice in Python
|
||||
-----------------------------------------
|
||||
|
||||
Instructions:
|
||||
|
||||
1. Just create a new class that inherits from this base class
|
||||
2. Define into the new class the variables
|
||||
_svc_name_ = "nameOfWinservice"
|
||||
_svc_display_name_ = "name of the Winservice that will be displayed in scm"
|
||||
_svc_description_ = "description of the Winservice that will be displayed in scm"
|
||||
3. Override the three main methods:
|
||||
def start(self) : if you need to do something at the service initialization.
|
||||
A good idea is to put here the inizialization of the running condition
|
||||
def stop(self) : if you need to do something just before the service is stopped.
|
||||
A good idea is to put here the invalidation of the running condition
|
||||
def main(self) : your actual run loop. Just create a loop based on your running condition
|
||||
4. Define the entry point of your module calling the method "parse_command_line" of the new class
|
||||
5. Enjoy
|
||||
'''
|
||||
|
||||
import socket
|
||||
|
||||
import win32serviceutil
|
||||
|
||||
import servicemanager
|
||||
import win32event
|
||||
import win32service
|
||||
|
||||
|
||||
class SMWinservice(win32serviceutil.ServiceFramework):
|
||||
'''Base class to create winservice in Python'''
|
||||
|
||||
_svc_name_ = 'pythonService'
|
||||
_svc_display_name_ = 'Python Service'
|
||||
_svc_description_ = 'Python Service Description'
|
||||
|
||||
@classmethod
|
||||
def parse_command_line(cls):
|
||||
'''
|
||||
ClassMethod to parse the command line
|
||||
'''
|
||||
win32serviceutil.HandleCommandLine(cls)
|
||||
|
||||
def __init__(self, args):
|
||||
'''
|
||||
Constructor of the winservice
|
||||
'''
|
||||
win32serviceutil.ServiceFramework.__init__(self, args)
|
||||
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
|
||||
socket.setdefaulttimeout(60)
|
||||
|
||||
def SvcStop(self):
|
||||
'''
|
||||
Called when the service is asked to stop
|
||||
'''
|
||||
self.stop()
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
win32event.SetEvent(self.hWaitStop)
|
||||
|
||||
def SvcDoRun(self):
|
||||
'''
|
||||
Called when the service is asked to start
|
||||
'''
|
||||
self.start()
|
||||
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STARTED,
|
||||
(self._svc_name_, ''))
|
||||
self.main()
|
||||
|
||||
def start(self):
|
||||
'''
|
||||
Override to add logic before the start
|
||||
eg. running condition
|
||||
'''
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
'''
|
||||
Override to add logic before the stop
|
||||
eg. invalidating running condition
|
||||
'''
|
||||
pass
|
||||
|
||||
def main(self):
|
||||
'''
|
||||
Main class to be ovverridden to add logic
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
# entry point of the module: copy and paste into the new module
|
||||
# ensuring you are calling the "parse_command_line" of the new created class
|
||||
if __name__ == '__main__':
|
||||
SMWinservice.parse_command_line()
|
2
install/debug.bat
Normal file
2
install/debug.bat
Normal file
|
@ -0,0 +1,2 @@
|
|||
python C:\Users\matth\Documents\GitHub\bapsicle\install\windows_service.py debug
|
||||
TIMEOUT 10
|
12
install/install.bat
Normal file
12
install/install.bat
Normal file
|
@ -0,0 +1,12 @@
|
|||
cd /D "%~dp0"
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-windows.txt
|
||||
pip install -e ..\
|
||||
python windows_service.py install
|
||||
|
||||
mkdir "C:\Program Files\BAPSicle"
|
||||
cd "C:\Program Files\BAPSicle\"
|
||||
mkdir state
|
||||
|
||||
copy "C:\Program Files\Python37\Lib\site-packages\pywin32_system32\pywintypes37.dll" "C:\Program Files\Python37\Lib\site-packages\win32\"
|
||||
TIMEOUT 10
|
1
install/requirements-windows.txt
Normal file
1
install/requirements-windows.txt
Normal file
|
@ -0,0 +1 @@
|
|||
pywin32
|
36
install/windows_service.py
Normal file
36
install/windows_service.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from server import BAPSicleServer
|
||||
from pathlib import Path
|
||||
from SMWinservice import SMWinservice
|
||||
import time
|
||||
import multiprocessing
|
||||
|
||||
import sys
|
||||
sys.path.append("..\\")
|
||||
|
||||
|
||||
class BAPScileAsAService(SMWinservice):
|
||||
_svc_name_ = "BAPSicle"
|
||||
_svc_display_name_ = "BAPSicle Server"
|
||||
_svc_description_ = "BAPS development has been frozen for a while, but this new spike of progress is dripping."
|
||||
|
||||
def start(self):
|
||||
self.isrunning = True
|
||||
self.server = multiprocessing.Process(target=BAPSicleServer).start()
|
||||
|
||||
def stop(self):
|
||||
print("stopping")
|
||||
self.isrunning = False
|
||||
try:
|
||||
self.server.terminate()
|
||||
self.server.join()
|
||||
except:
|
||||
pass
|
||||
|
||||
def main(self):
|
||||
while self.isrunning:
|
||||
time.sleep(1)
|
||||
print("BAPSicle is running.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BAPScileAsAService.parse_command_line()
|
231
player.py
231
player.py
|
@ -6,144 +6,147 @@ import copy
|
|||
|
||||
from state_manager import StateManager
|
||||
|
||||
|
||||
class Player():
|
||||
state = None
|
||||
state = None
|
||||
running = False
|
||||
|
||||
__default_state = {
|
||||
"filename": "",
|
||||
"channel": -1,
|
||||
"playing": False,
|
||||
"pos": 0,
|
||||
"remaining": 0,
|
||||
"length": 0,
|
||||
"loop": False,
|
||||
"output": None
|
||||
}
|
||||
__default_state = {
|
||||
"filename": "",
|
||||
"channel": -1,
|
||||
"playing": False,
|
||||
"pos": 0,
|
||||
"remaining": 0,
|
||||
"length": 0,
|
||||
"loop": False,
|
||||
"output": None
|
||||
}
|
||||
|
||||
def isInit(self):
|
||||
try:
|
||||
pygame.mixer.music.get_busy()
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
def isInit(self):
|
||||
try:
|
||||
pygame.mixer.music.get_busy()
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def isPlaying(self):
|
||||
return bool(pygame.mixer.music.get_busy())
|
||||
def isPlaying(self):
|
||||
return bool(pygame.mixer.music.get_busy())
|
||||
|
||||
def play(self):
|
||||
def play(self):
|
||||
|
||||
pygame.mixer.music.play(0)
|
||||
|
||||
pygame.mixer.music.play(0)
|
||||
def pause(self):
|
||||
pygame.mixer.music.pause()
|
||||
|
||||
def pause(self):
|
||||
pygame.mixer.music.pause()
|
||||
def unpause(self):
|
||||
pygame.mixer.music.play(0, self.state.state["pos"])
|
||||
|
||||
def unpause(self):
|
||||
pygame.mixer.music.play(0, self.state.state["pos"])
|
||||
def stop(self):
|
||||
pygame.mixer.music.stop()
|
||||
|
||||
def stop(self):
|
||||
pygame.mixer.music.stop()
|
||||
def seek(self, pos):
|
||||
if self.isPlaying():
|
||||
pygame.mixer.music.play(0, pos)
|
||||
else:
|
||||
self.updateState(pos)
|
||||
|
||||
def seek(self, pos):
|
||||
if self.isPlaying():
|
||||
pygame.mixer.music.play(0, pos)
|
||||
else:
|
||||
self.updateState(pos)
|
||||
def load(self, filename):
|
||||
if not self.isPlaying():
|
||||
self.state.update("filename", filename)
|
||||
pygame.mixer.music.load(filename)
|
||||
if ".mp3" in filename:
|
||||
song = MP3(filename)
|
||||
self.state.update("length", song.info.length)
|
||||
else:
|
||||
self.state.update("length", pygame.mixer.Sound(filename).get_length()/1000)
|
||||
|
||||
def load(self, filename):
|
||||
if not self.isPlaying():
|
||||
self.state.update("filename",filename)
|
||||
pygame.mixer.music.load(filename)
|
||||
if ".mp3" in filename:
|
||||
song = MP3(filename)
|
||||
self.state.update("length",song.info.length)
|
||||
else:
|
||||
self.state.update("length",pygame.mixer.Sound(filename).get_length()/1000)
|
||||
def quit(self):
|
||||
pygame.mixer.quit()
|
||||
|
||||
def output(self, name = None):
|
||||
pygame.mixer.quit()
|
||||
try:
|
||||
if name:
|
||||
pygame.mixer.init(44100, -16, 1, 1024, devicename=name)
|
||||
else:
|
||||
pygame.mixer.init(44100, -16, 1, 1024)
|
||||
except:
|
||||
return "FAIL:Failed to init mixer, check sound devices."
|
||||
else:
|
||||
self.state.update("output",name)
|
||||
|
||||
return "OK"
|
||||
def output(self, name=None):
|
||||
self.quit()
|
||||
try:
|
||||
if name:
|
||||
pygame.mixer.init(44100, -16, 1, 1024, devicename=name)
|
||||
else:
|
||||
pygame.mixer.init(44100, -16, 1, 1024)
|
||||
except:
|
||||
return "FAIL:Failed to init mixer, check sound devices."
|
||||
else:
|
||||
self.state.update("output", name)
|
||||
|
||||
return "OK"
|
||||
|
||||
def updateState(self, pos = None):
|
||||
self.state.update("playing",self.isPlaying())
|
||||
if (pos):
|
||||
self.state.update("pos",max(0,pos))
|
||||
else:
|
||||
self.state.update("pos",max(0,pygame.mixer.music.get_pos()/1000))
|
||||
self.state.update("remaining",self.state.state["length"] - self.state.state["pos"])
|
||||
def updateState(self, pos=None):
|
||||
self.state.update("playing", self.isPlaying())
|
||||
if (pos):
|
||||
self.state.update("pos", max(0, pos))
|
||||
else:
|
||||
self.state.update("pos", max(0, pygame.mixer.music.get_pos()/1000))
|
||||
self.state.update("remaining", self.state.state["length"] - self.state.state["pos"])
|
||||
|
||||
def getDetails(self):
|
||||
res = "RESP:DETAILS: " + json.dumps(self.state.state)
|
||||
return res
|
||||
def getDetails(self):
|
||||
res = "RESP:DETAILS: " + json.dumps(self.state.state)
|
||||
return res
|
||||
|
||||
def __init__(self, channel, in_q, out_q):
|
||||
self.running = True
|
||||
|
||||
def __init__(self, channel, in_q, out_q):
|
||||
self.state = StateManager("channel" + str(channel), self.__default_state)
|
||||
|
||||
self.state = StateManager("channel" + str(channel), self.__default_state)
|
||||
self.state.update("channel", channel)
|
||||
|
||||
self.state.update("channel", channel)
|
||||
loaded_state = copy.copy(self.state.state)
|
||||
|
||||
loaded_state = copy.copy(self.state.state)
|
||||
if loaded_state["output"]:
|
||||
print("Setting output to: " + loaded_state["output"])
|
||||
self.output(loaded_state["output"])
|
||||
else:
|
||||
self.output()
|
||||
|
||||
if loaded_state["output"]:
|
||||
print("Setting output to: " + loaded_state["output"])
|
||||
self.output(loaded_state["output"])
|
||||
else:
|
||||
self.output()
|
||||
if loaded_state["filename"]:
|
||||
print("Loading filename: " + loaded_state["filename"])
|
||||
self.load(loaded_state["filename"])
|
||||
|
||||
if loaded_state["filename"]:
|
||||
print("Loading filename: " + loaded_state["filename"])
|
||||
self.load(loaded_state["filename"])
|
||||
if loaded_state["pos"] != 0:
|
||||
print("Seeking to pos: " + str(loaded_state["pos"]))
|
||||
self.seek(loaded_state["pos"])
|
||||
|
||||
if loaded_state["pos"] != 0:
|
||||
print("Seeking to pos: " + str(loaded_state["pos"]))
|
||||
self.seek(loaded_state["pos"])
|
||||
if loaded_state["playing"] == True:
|
||||
print("Resuming.")
|
||||
self.unpause()
|
||||
|
||||
if loaded_state["playing"] == True:
|
||||
print("Resuming.")
|
||||
self.unpause()
|
||||
while self.running:
|
||||
time.sleep(0.01)
|
||||
incoming_msg = in_q.get()
|
||||
if (not incoming_msg):
|
||||
continue
|
||||
if self.isInit():
|
||||
self.updateState()
|
||||
if (incoming_msg == 'PLAY'):
|
||||
self.play()
|
||||
if (incoming_msg == 'PAUSE'):
|
||||
self.pause()
|
||||
if (incoming_msg == 'UNPAUSE'):
|
||||
self.unpause()
|
||||
if (incoming_msg == 'STOP'):
|
||||
self.stop()
|
||||
if (incoming_msg == 'QUIT'):
|
||||
self.quit()
|
||||
self.running = False
|
||||
if (incoming_msg.startswith("SEEK")):
|
||||
split = incoming_msg.split(":")
|
||||
self.seek(float(split[1]))
|
||||
if (incoming_msg.startswith("LOAD")):
|
||||
split = incoming_msg.split(":")
|
||||
self.load(split[1])
|
||||
if (incoming_msg == 'DETAILS'):
|
||||
out_q.put(self.getDetails())
|
||||
|
||||
if (incoming_msg.startswith("OUTPUT")):
|
||||
split = incoming_msg.split(":")
|
||||
out_q.put(self.output(split[1]))
|
||||
|
||||
|
||||
|
||||
while True:
|
||||
time.sleep(0.01)
|
||||
incoming_msg = in_q.get()
|
||||
if (not incoming_msg):
|
||||
continue
|
||||
if self.isInit():
|
||||
self.updateState()
|
||||
if (incoming_msg == 'PLAY'):
|
||||
self.play()
|
||||
if (incoming_msg == 'PAUSE'):
|
||||
self.pause()
|
||||
if (incoming_msg == 'UNPAUSE'):
|
||||
self.unpause()
|
||||
if (incoming_msg == 'STOP'):
|
||||
self.stop()
|
||||
if (incoming_msg.startswith("SEEK")):
|
||||
split = incoming_msg.split(":")
|
||||
self.seek(float(split[1]))
|
||||
if (incoming_msg.startswith("LOAD")):
|
||||
split = incoming_msg.split(":")
|
||||
self.load(split[1])
|
||||
if (incoming_msg == 'DETAILS'):
|
||||
out_q.put(self.getDetails())
|
||||
|
||||
|
||||
if (incoming_msg.startswith("OUTPUT")):
|
||||
split = incoming_msg.split(":")
|
||||
out_q.put(self.output(split[1]))
|
||||
|
||||
print("Quiting player ", channel)
|
||||
|
|
30
server.py
30
server.py
|
@ -1,9 +1,18 @@
|
|||
import multiprocessing
|
||||
import player
|
||||
from flask import Flask, render_template, send_from_directory
|
||||
from flask import Flask, render_template, send_from_directory, request
|
||||
import json
|
||||
import sounddevice as sd
|
||||
|
||||
|
||||
class BAPSicleServer():
|
||||
def __init__(self):
|
||||
startServer()
|
||||
|
||||
def __del__(self):
|
||||
stopServer()
|
||||
|
||||
|
||||
app = Flask(__name__, static_url_path='')
|
||||
|
||||
channel_to_q = []
|
||||
|
@ -135,8 +144,7 @@ def send_static(path):
|
|||
return send_from_directory('ui-static', path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
def startServer():
|
||||
for channel in range(3):
|
||||
channel_to_q.append(multiprocessing.Queue())
|
||||
channel_from_q.append(multiprocessing.Queue())
|
||||
|
@ -144,10 +152,24 @@ if __name__ == "__main__":
|
|||
channel_p.append(
|
||||
multiprocessing.Process(
|
||||
target=player.Player,
|
||||
args=(channel, channel_to_q[-1], channel_from_q[-1])
|
||||
args=(channel, channel_to_q[-1], channel_from_q[-1]),
|
||||
daemon=True
|
||||
)
|
||||
)
|
||||
channel_p[channel].start()
|
||||
|
||||
# Don't use reloader, it causes Nested Processes!
|
||||
app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=False)
|
||||
|
||||
|
||||
def stopServer():
|
||||
print("Stopping server.py")
|
||||
for q in channel_to_q:
|
||||
q.put("QUIT")
|
||||
for player in channel_p:
|
||||
player.join()
|
||||
app = None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("BAPSicle is a service. Please run it like one.")
|
||||
|
|
|
@ -7,7 +7,7 @@ class StateManager:
|
|||
__state = {}
|
||||
|
||||
def __init__(self, name, default_state=None):
|
||||
self.filepath = "state/" + name + ".json"
|
||||
self.filepath = "C:\Program Files\BAPSicle\state\\" + name + ".json"
|
||||
if not os.path.isfile(self.filepath):
|
||||
self.log("No file found for " + self.filepath)
|
||||
try:
|
||||
|
|
Loading…
Reference in a new issue