Skip to content

Commit

Permalink
add custom_startup code
Browse files Browse the repository at this point in the history
  • Loading branch information
Shotgunosine committed Dec 19, 2024
1 parent 008edf6 commit d474506
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 17 deletions.
4 changes: 4 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@

CURRENT_OS: str = platform.system()
API_BASE_URL: str = 'https://mlc.nimh.nih.gov/cogmood'
#API_BASE_URL='http://127.0.0.1:5000'
API_SALT: str = 'SALT'
VERIFY: bool = False
RUNNING_FROM_EXECUTABLE: bool = getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')
# WORKER_ID_SOURCE valid values = ['USER', 'EXECUTABLE']
WORKER_ID_SOURCE = "USER"
# WORKER_ID_PLACEHOLDER_VALUE is the placeholder value assigned to the WorkerID field
# in the executables when we build them. It should be replaced by actual ID when
# the executable is prepared for distribution.
Expand Down
86 changes: 86 additions & 0 deletions custom_startup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from smile.common import Experiment, Log, Wait, Func, UntilDone, \
Label, Loop, If, Elif, Else, KeyPress, Ref, \
Parallel, Slider, Serial, UpdateWidget, Debug, Meanwhile, While, Subroutine
from smile.video import Rectangle, TextInput, Button, ButtonPress
from smile.mouse import MouseCursor
from smile.startup import (
INFO_WIDTH,
INFO_HEIGHT,
INFO_OUTLINE_COLOR,
INFO_COLOR,
INFO_FONT_SIZE,
INFO_BUTTON_HEIGHT,
INFO_BUTTON_WIDTH,
TEXT_INPUT_WIDTH,
TEXT_INPUT_HEIGHT
)
from smile.scale import scale as s
from hashlib import blake2b

import config as CogBatt_config

def _validate_code(exp):
worker_id = exp._subject
code = exp.get_var('_code')
expected_code = blake2b(worker_id.encode(), digest_size=4, salt=CogBatt_config.API_SALT.encode()).hexdigest()[:4]
Debug(code=code, expected_code=expected_code, invalid=code!=expected_code)
exp.set_var('code_invalid', code != expected_code)

@Subroutine
def InputSubject(self):
with Parallel():
with Parallel(blocking=False):
MouseCursor()
recOut = Rectangle(width=s(INFO_WIDTH) + s(20),
height=s(INFO_HEIGHT) + s(20),
color=INFO_OUTLINE_COLOR)
recin = Rectangle(width=s(INFO_WIDTH),
height=s(INFO_HEIGHT),
color=INFO_COLOR)
lbl = Label(text=CogBatt_config.EXP_NAME, center_x=recin.center_x,
top=recin.top - s(10),
font_size=s(INFO_FONT_SIZE))
idIn = TextInput(width=s(TEXT_INPUT_WIDTH),
height=s(TEXT_INPUT_HEIGHT),
font_size=s(INFO_FONT_SIZE),
center_x=recin.center_x,
top=lbl.bottom - s(20),
multiline=False,
text="",
disabled=False,
hint_text="Prolific Worker ID",
write_tab=False)
codeIn = TextInput(width=s(TEXT_INPUT_WIDTH),
height=s(TEXT_INPUT_HEIGHT),
font_size=s(INFO_FONT_SIZE),
center_x=recin.center_x,
top=lbl.bottom - s(80),
multiline=False,
text="",
disabled=False,
hint_text="4 digit task code",
write_tab=False)
bc = Button(text="Continue", font_size=s(INFO_FONT_SIZE),
height=s(INFO_BUTTON_HEIGHT),
width=s(INFO_BUTTON_WIDTH),
right=recin.right - s(20),
bottom=recin.bottom + s(20),
name="C",
background_normal="",
background_color=INFO_OUTLINE_COLOR,
disabled=True)
with Serial():
with While(
(Ref.object(codeIn.text).__len__() < 4)
or (Ref(str, idIn.text) == '')
):
Wait(0.1)
bc.disabled = False

bp = ButtonPress(buttons=[bc])
with If(
(bp.pressed == "C")
):
Func(self.exp._change_smile_subj, Ref.object(idIn.text).lower().strip())
Func(self.exp.set_var, '_code', Ref.object(codeIn.text).lower().strip())
Func(_validate_code, Ref.object(self.exp))
43 changes: 31 additions & 12 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
# Smile imports
from smile.common import Experiment, Log, Wait, Func, UntilDone, \
Label, Loop, If, Elif, Else, KeyPress, Ref, \
Parallel, Slider, Serial, UpdateWidget, Debug, Meanwhile
from smile.clock import clock
Parallel, Slider, Serial, UpdateWidget, Debug, Meanwhile, While
from smile.scale import scale as s
from custom_startup import InputSubject
# from android.permissions import request_permissions, Permission
# request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE])
from kivy.resources import resource_add_path
Expand Down Expand Up @@ -64,22 +64,35 @@
WRK_DIR = sys._MEIPASS
resource_add_path(WRK_DIR)

retrieved_worker_id = retrieve_worker_id()

tasks_from_api = get_blocks_to_run(retrieved_worker_id['content'])
number_of_tasks = 0 if tasks_from_api['status'] == 'error' else len(tasks_from_api['content'])


# Initialize the SMILE experiment.
exp = Experiment(name=CogBatt_config.EXP_NAME,
background_color=CogBatt_config.BACKGROUND_COLOR,
scale_down=True, scale_box=(1000, 1000), debug=False,
scale_down=True, scale_box=(1000, 1000), debug=True,
Touch=False, local_crashlog=True,
cmd_traceback=False, data_dir=WRK_DIR,
working_dir=WRK_DIR)
exp._code = ''
if CogBatt_config.WORKER_ID_SOURCE == 'EXECUTABLE':
retrieved_worker_id = retrieve_worker_id()
tasks_from_api = get_blocks_to_run(retrieved_worker_id['content'])
number_of_tasks = 0 if tasks_from_api['status'] == 'error' else len(tasks_from_api['content'])

exp.tasks_from_api = tasks_from_api
exp.worker_id_dict = retrieved_worker_id
elif CogBatt_config.WORKER_ID_SOURCE == 'USER':
InputSubject()
tasks_from_api = Func(get_blocks_to_run, Ref.object(exp)._subject, Ref.object(exp).get_var('_code')).result
with If(tasks_from_api['status'] == 'error'):
number_of_tasks = 0
with Else():
number_of_tasks = tasks_from_api['content'].__len__()
exp.tasks_from_api = tasks_from_api
exp.worker_id_dict = {"status": "success", "content": Ref.object(exp)._subject}
else:
raise NotImplementedError


exp.tasks_from_api = tasks_from_api
exp.worker_id_dict = retrieved_worker_id

with Parallel():
with Serial(blocking=False):
Expand All @@ -89,8 +102,13 @@
author=version.__author__,
date_time=version.__date__,
email=version.__email__)

Wait(.5)
with If(Ref.object(exp).get_var('code_invalid')):
error_screen(error='Invalid task code: ' + Ref(str, exp._code),
message='You entered an incorrect task code, please double check the code '
'listed on the website and try again. If it still does not work '
'please contact Dylan Nielson at [email protected].'
)

with If(CogBatt_config.RUNNING_FROM_EXECUTABLE):
# Handles case where retrieval of worker id fails
Expand Down Expand Up @@ -196,7 +214,8 @@
block_name=exp.task_name + '_' + Ref(str, exp.block_number),
data_directory=Ref.object(
exp)._session_dir,
slog_file_name='log_'+exp.task_name+'_'+'0.slog')
slog_file_name='log_'+exp.task_name+'_'+'0.slog',
code=Ref.object(exp).get_var('_code'))
Wait(3)

# Error screen for failed upload
Expand Down
19 changes: 14 additions & 5 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import requests
import logging
import time
from config import API_BASE_URL, RUNNING_FROM_EXECUTABLE, CURRENT_OS, VERIFY
from config import API_BASE_URL, RUNNING_FROM_EXECUTABLE, CURRENT_OS, VERIFY, WORKER_ID_SOURCE
from hashlib import blake2b
from io import BytesIO
from pathlib import Path
Expand All @@ -17,18 +17,22 @@
format='%(asctime)s - %(levelname)s - %(message)s')


def get_blocks_to_run(worker_id: str) -> list[str] | dict[str, str]:
def get_blocks_to_run(worker_id: str, code: str | None = None) -> list[str] | dict[str, str]:
"""
Sends a GET request to retrieve the list of blocks that are yet to be run by the worker.
Args:
worker_id (str): The unique identifier for the worker whose blocks are being fetched.
code (str): Optional verifcation code if worker_id_source is user
Returns:
dict: A dictionary with 'status' as success or error, and 'content' containing either the blocks list or an error message.
"""
url = f'{API_BASE_URL}/taskcontrol'
params = {'worker_id': worker_id}
if WORKER_ID_SOURCE == 'EXECUTABLE':
params = {'worker_id': worker_id}
elif WORKER_ID_SOURCE == 'USER':
params = {'worker_id': worker_id, 'code':code}

try:
response = requests.get(url, params=params, verify=VERIFY)
Expand Down Expand Up @@ -78,7 +82,7 @@ def hash_file(file_obj):
return hash_blake.hexdigest()


def upload_block(worker_id: str, block_name: str, data_directory: str, slog_file_name: str) -> dict[str, str]:
def upload_block(worker_id: str, block_name: str, data_directory: str, slog_file_name: str, code: str | None = None) -> dict[str, str]:
"""
Sends a POST request to upload a completed block along with its checksum and the associated zipped file.
Uses the config.API_BASE_URL to build the URL.
Expand All @@ -88,6 +92,7 @@ def upload_block(worker_id: str, block_name: str, data_directory: str, slog_file
block_name (str): The name of the block being uploaded, typically in the format "taskname_runnumber".
data_directory (str): The directory where the slog file is located.
slog_file_name (str): The name of the slog file.
code (str): Optional verifcation code if worker_id_source is user.
Behavior:
- Zips the slog file.
Expand Down Expand Up @@ -119,7 +124,10 @@ def upload_block(worker_id: str, block_name: str, data_directory: str, slog_file
current_time_ms = int(time.time() * 1000)
zip_file_name_with_timestamp = f'{block_name}_{current_time_ms}.zip'

params = {'worker_id': worker_id}
if WORKER_ID_SOURCE == 'EXECUTABLE':
params = {'worker_id': worker_id}
elif WORKER_ID_SOURCE == 'USER':
params = {'worker_id': worker_id, 'code': code}
data = {'block_name': block_name, 'checksum': checksum}
files = {'file': (zip_file_name_with_timestamp, zip_buffer, 'application/zip')}

Expand Down Expand Up @@ -243,6 +251,7 @@ def _read_exe_worker_id() -> dict[str, str]:
return {"status": "error", "content": str(e)}



if __name__ == "__main__":
upload_block(worker_id='123456',
block_name='flkr_1',
Expand Down

0 comments on commit d474506

Please sign in to comment.