Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add custom_startup code #7

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
41 changes: 30 additions & 11 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,11 +64,6 @@
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,
Expand All @@ -77,9 +72,27 @@
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
Loading