Skip to content

Commit

Permalink
Add logging
Browse files Browse the repository at this point in the history
  • Loading branch information
MaddyGuthridge committed Jan 17, 2024
1 parent 596a943 commit 4388d08
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 26 deletions.
38 changes: 33 additions & 5 deletions flapi/__comms.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
`repr` doesn't provide a complete reconstruction) cannot be shared.
"""
import time
import logging
from mido import Message as MidoMsg # type: ignore
from typing import Any, Optional
from .__util import try_eval
Expand All @@ -63,6 +64,9 @@
)


log = logging.getLogger(__name__)


def send_msg(msg: bytes):
"""
Send a message to FL Studio
Expand All @@ -71,8 +75,8 @@ def send_msg(msg: bytes):
getContext().port.send(mido_msg)


def handle_stdout(output: bytes):
print(output.decode(), end='')
def handle_stdout(output: str):
print(output, end='')


def handle_received_message(msg: bytes) -> Optional[bytes]:
Expand All @@ -84,11 +88,13 @@ def handle_received_message(msg: bytes) -> Optional[bytes]:
# Handle universal device enquiry
if msg == consts.DEVICE_ENQUIRY_MESSAGE:
# Send the response
log.debug('Received universal device enquiry')
send_msg(consts.DEVICE_ENQUIRY_RESPONSE)
return None

# Handle invalid message types
if not msg.startswith(consts.SYSEX_HEADER):
log.debug('Received unrecognised message')
raise FlapiInvalidMsgError(msg)

# Handle loopback (prevent us from receiving our own messages)
Expand All @@ -100,12 +106,16 @@ def handle_received_message(msg: bytes) -> Optional[bytes]:

# Handle FL Studio stdout
if msg.removeprefix(consts.SYSEX_HEADER)[1] == consts.MSG_TYPE_STDOUT:
handle_stdout(msg.removeprefix(consts.SYSEX_HEADER)[3:])
text = msg.removeprefix(consts.SYSEX_HEADER)[3:].decode()
log.debug(f"Received server stdout: {text}")
handle_stdout(text)
return None

# Handle exit command
if msg.removeprefix(consts.SYSEX_HEADER)[1] == consts.MSG_TYPE_EXIT:
exit(int(msg.removeprefix(consts.SYSEX_HEADER)[3:].decode()))
code = int(msg.removeprefix(consts.SYSEX_HEADER)[3:].decode())
log.info(f"Received exit command with code {code}")
exit(code)

# Normal processing
return msg[len(consts.SYSEX_HEADER) + 1:]
Expand All @@ -122,8 +132,16 @@ def assert_response_is_ok(msg: bytes, expected_msg_type: int):
msg_type = msg[0]

if msg_type != expected_msg_type:
expected = consts.MSG_TYPE_NAMES.get(
expected_msg_type,
str(expected_msg_type),
)
actual = consts.MSG_TYPE_NAMES.get(
msg_type,
str(msg_type),
)
raise FlapiClientError(
f"Expected message type {expected_msg_type}, received {msg_type}")
f"Expected message type '{expected}', received '{actual}'")

msg_status = msg[1]

Expand Down Expand Up @@ -180,27 +198,32 @@ def heartbeat() -> bool:
If no data is received, this function returns `False`.
"""
log.debug("heartbeat")
try:
send_msg(consts.SYSEX_HEADER + bytes([
consts.MSG_FROM_CLIENT,
consts.MSG_TYPE_HEARTBEAT,
]))
response = receive_message()
assert_response_is_ok(response, consts.MSG_TYPE_HEARTBEAT)
log.debug("heartbeat: passed")
return True
except FlapiTimeoutError:
log.debug("heartbeat: failed")
return False


def version_query() -> tuple[int, int, int]:
"""
Query and return the version of Flapi installed to FL Studio.
"""
log.debug("version_query")
send_msg(
consts.SYSEX_HEADER
+ bytes([consts.MSG_FROM_CLIENT, consts.MSG_TYPE_VERSION_QUERY])
)
response = receive_message()
log.debug("version_query: got response")

assert_response_is_ok(response, consts.MSG_TYPE_VERSION_QUERY)

Expand All @@ -215,12 +238,14 @@ def fl_exec(code: str) -> None:
"""
Output Python code to FL Studio, where it will be executed.
"""
log.debug(f"fl_exec: {code}")
send_msg(
consts.SYSEX_HEADER
+ bytes([consts.MSG_FROM_CLIENT, consts.MSG_TYPE_EXEC])
+ code.encode()
)
response = receive_message()
log.debug("fl_exec: got response")

assert_response_is_ok(response, consts.MSG_TYPE_EXEC)

Expand All @@ -230,12 +255,14 @@ def fl_eval(expression: str) -> Any:
Output a Python expression to FL Studio, where it will be evaluated, with
the result being returned.
"""
log.debug(f"fl_eval: {expression}")
send_msg(
consts.SYSEX_HEADER
+ bytes([consts.MSG_FROM_CLIENT, consts.MSG_TYPE_EVAL])
+ expression.encode()
)
response = receive_message()
log.debug("fl_eval: got response")

assert_response_is_ok(response, consts.MSG_TYPE_EVAL)

Expand All @@ -247,6 +274,7 @@ def fl_print(text: str):
"""
Print the given text to FL Studio's Python console.
"""
log.debug(f"fl_print (not expecting response): {text}")
send_msg(
consts.SYSEX_HEADER
+ bytes([consts.MSG_FROM_CLIENT, consts.MSG_TYPE_STDOUT])
Expand Down
9 changes: 6 additions & 3 deletions flapi/__decorate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
Code for decorating the FL Studio API libraries to enable Flapi
"""
from types import FunctionType
import logging
import inspect
import importlib
from types import FunctionType
from typing import Callable, TypeVar
from typing_extensions import ParamSpec
from functools import wraps
Expand All @@ -15,6 +16,8 @@
P = ParamSpec('P')
R = TypeVar('R')

log = logging.getLogger(__name__)


ApiCopyType = dict[str, dict[str, FunctionType]]

Expand Down Expand Up @@ -49,11 +52,10 @@ def add_wrappers() -> ApiCopyType:
For each FL Studio module, replace its items with a decorated version that
evaluates the function inside FL Studio.
"""
log.info("Adding wrappers to API stubs")

modules: ApiCopyType = {}

for mod_name in FL_MODULES:

modules[mod_name] = {}

mod = importlib.import_module(mod_name)
Expand All @@ -74,6 +76,7 @@ def restore_original_functions(backup: ApiCopyType):
Restore the original FL Studio API Stubs functions - called when
deactivating Flapi.
"""
log.info("Removing wrappers from API stubs")
for mod_name, functions in backup.items():
mod = importlib.import_module(mod_name)

Expand Down
26 changes: 22 additions & 4 deletions flapi/__enable.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Code for initializing/closing Flapi
"""
import logging
import mido # type: ignore
from typing import Protocol, Generic, TypeVar, Optional
from mido.ports import BaseOutput, BaseInput, IOPort # type: ignore
Expand All @@ -13,6 +14,9 @@
from .errors import FlapiPortError, FlapiConnectionError, FlapiVersionError


log = logging.getLogger(__name__)


T = TypeVar('T', BaseInput, BaseOutput, covariant=True)


Expand Down Expand Up @@ -65,11 +69,24 @@ def enable(port_name: str = _consts.DEFAULT_PORT_NAME) -> bool:
will need to call `init()` once FL Studio is running and configured
correctly.
"""
log.info(f"Enable Flapi client on port '{port_name}'")
# First, connect to all the MIDI ports
res = open_port(
port_name, mido.get_input_names(), mido.open_input) # type: ignore
req = open_port(
port_name, mido.get_output_names(), mido.open_output) # type: ignore
inputs = mido.get_input_names() # type: ignore
outputs = mido.get_output_names() # type: ignore

log.info(f"Available inputs are: {inputs}")
log.info(f"Available outputs are: {outputs}")

try:
res = open_port(port_name, inputs, mido.open_input) # type: ignore
except Exception:
log.exception("Error when connecting to input")
raise
try:
req = open_port(port_name, outputs, mido.open_output) # type: ignore
except Exception:
log.exception("Error when connecting to output")
raise

if res is None or req is None:
try:
Expand All @@ -79,6 +96,7 @@ def enable(port_name: str = _consts.DEFAULT_PORT_NAME) -> bool:
)
except NotImplementedError as e:
# Port could not be opened
log.exception("Could not open create new port")
raise FlapiPortError(port_name) from e
else:
port = IOPort(res, req)
Expand Down
12 changes: 12 additions & 0 deletions flapi/_consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@
certain exit codes could break the MIDI spec)
"""

MSG_TYPE_NAMES = {
MSG_TYPE_HEARTBEAT: "heartbeat",
MSG_TYPE_VERSION_QUERY: "version query",
MSG_TYPE_EXEC: "exec",
MSG_TYPE_EVAL: "eval",
MSG_TYPE_STDOUT: "stdout",
MSG_TYPE_EXIT: "exit",
}
"""
Names of message types
"""

MSG_STATUS_OK = 0x00
"""
Message was processed correctly.
Expand Down
3 changes: 3 additions & 0 deletions flapi/cli/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@


CONNECTION_TIMEOUT = 60.0
"""
The maximum duration to wait for a connection with FL Studio
"""
4 changes: 4 additions & 0 deletions flapi/cli/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from flapi import _consts as consts
from flapi.cli import consts as cli_consts
from .util import handle_verbose
try:
import IPython
from IPython import start_ipython
Expand Down Expand Up @@ -196,12 +197,15 @@ def start_ipython_shell():
help="Maximum time to wait to establish a connection with FL Studio",
default=cli_consts.CONNECTION_TIMEOUT,
)
@click.option('-v', '--verbose', count=True)
def repl(
shell: Optional[str] = None,
port: str = consts.DEFAULT_PORT_NAME,
timeout: float = cli_consts.CONNECTION_TIMEOUT,
verbose: int = 0,
):
"""Main function to set up the Python shell"""
handle_verbose(verbose)
print("Flapi interactive shell")
print(f"Client version: {flapi.__version__}")
print(f"Python version: {sys.version}")
Expand Down
17 changes: 3 additions & 14 deletions flapi/cli/uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,16 @@
prompt=True,
help="The path of the Image-Line data directory. Set to '-' for default",
)
@click.option(
"-y",
"--yes",
is_flag=True,
help="Proceed with uninstallation without confirmation",
prompt="Are you sure you want to uninstall the Flapi server?"
@click.confirmation_option(
prompt="Are you sure you want to uninstall the Flapi server?",
)
def uninstall(
data_dir: Path,
yes: bool = False,
):
def uninstall(data_dir: Path):
"""
Uninstall the Flapi server
"""
# Determine scripts folder location
server_location = output_dir(data_dir)

if not yes:
print("Operation cancelled")
exit(1)

# Remove it
rmtree(server_location)
print("Success!")
10 changes: 10 additions & 0 deletions flapi/cli/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
"""
from typing import Optional
from pathlib import Path
import logging


def handle_verbose(verbose: int):
if verbose == 0:
return
elif verbose == 1:
logging.basicConfig(level="INFO")
else:
logging.basicConfig(level="DEBUG")


def yn_prompt(prompt: str, default: Optional[bool] = None) -> bool:
Expand Down

0 comments on commit 4388d08

Please sign in to comment.