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

feat(share): add sharing using magic-wormhole #223

Merged
merged 81 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
8c0a78d
Copy from closed PR magic wormhole working code and test cases
Mustaballer Jun 5, 2023
b4b7879
Merge branch 'main' into share-magic-wormhole
Mustaballer Jun 15, 2023
395fa00
Merge branch 'main' into share-magic-wormhole
Mustaballer Jun 16, 2023
5fbdc1a
Merge remote-tracking branch 'upstream/main' into share-magic-wormhole
Mustaballer Jun 23, 2023
0a0208e
Merge branch 'share-magic-wormhole' of https://github.com/Mustaballer…
Mustaballer Jun 23, 2023
9dc1850
modify export_sql to use paramerterized queries to prevent sql injection
Mustaballer Jun 23, 2023
53f11f5
try resolve merge conflict with poetry.lock
Mustaballer Jun 23, 2023
0977859
Merge branch 'main' into share-magic-wormhole
Mustaballer Jun 23, 2023
cbee5e8
fix merge conflict and use better approach for overwriting env
Mustaballer Jun 23, 2023
9819330
Remove unnecessary function and pass test cases
Mustaballer Jun 25, 2023
afd9810
reformat file and group constants together in config.py
Mustaballer Jun 25, 2023
2211f78
ran black
Mustaballer Jun 25, 2023
f58fd5f
ran black
Mustaballer Jun 25, 2023
dafca05
moved functions in crud.py to db.py
Mustaballer Jun 26, 2023
3434a00
use tempfile in test_share.py and address minor changes
Mustaballer Jun 26, 2023
b801caf
restore original db name instead of literal
Mustaballer Jun 26, 2023
4c59c10
ran black -l 60, and it used call chain
Mustaballer Jun 26, 2023
088f370
Merge branch 'main' into share-magic-wormhole
0dm Jun 29, 2023
1096a15
Merge branch 'main' into share-magic-wormhole
Mustaballer Jul 4, 2023
01eef24
Add exception handling when ctrl+c during sharing that deletes db and…
Mustaballer Jul 4, 2023
5e52b5c
ran black and update poetry.lock
Mustaballer Jul 4, 2023
8317634
Add missing docstring in db.py
Mustaballer Jul 5, 2023
e675227
delete temp .env if ctrl+c during sharing
Mustaballer Jul 5, 2023
04c69b8
merge with latest main
Mustaballer Jul 6, 2023
2d54e83
Add .env.example and generate env in config.py
Mustaballer Jul 10, 2023
3992d1c
use .env.example for creating .env and removed unnecessary exceptions
Mustaballer Jul 12, 2023
d4d3f1c
Merge branch 'main' into share-magic-wormhole
Mustaballer Jul 17, 2023
2f2c0dd
use new approach for copying db
Mustaballer Jul 19, 2023
7a67386
Merge branch 'share-magic-wormhole' of https://github.com/Mustaballer…
Mustaballer Jul 19, 2023
6196876
modify copy deb function to return recording data and remov comments …
Mustaballer Jul 19, 2023
5893b3e
modify env names and add asserts to share.py
Mustaballer Jul 20, 2023
2149adc
copy alembic migrations
Mustaballer Jul 20, 2023
2f1548d
Copy all data relating to recording_timestamp in all tables
Mustaballer Jul 20, 2023
625c192
extract db file upon receiving recording
Mustaballer Jul 21, 2023
18a4918
add command to visualize recording
Mustaballer Jul 22, 2023
9b7f7af
remove unnecessary function and todo comment
Mustaballer Jul 22, 2023
786e063
refactor copy_recording_data
Mustaballer Jul 22, 2023
644c83a
remove unittest class and fix failing test case for receiving recordi…
Mustaballer Jul 22, 2023
653e785
modify conftest.py and fixtures.sql to insert data to every table for…
Mustaballer Jul 22, 2023
76f14b0
update unit tests
Mustaballer Jul 24, 2023
ada2aad
Merge branch 'main' into share-magic-wormhole
Mustaballer Jul 25, 2023
2f66b8a
resolve merge conflicts
Mustaballer Jul 25, 2023
f29f199
address flake8 errors
Mustaballer Jul 25, 2023
1a10f8a
Merge branch 'main' into share-magic-wormhole
Mustaballer Jul 31, 2023
c3173f8
resolve merge issues
Mustaballer Aug 1, 2023
ea25b53
Update openadapt/share.py
Mustaballer Aug 1, 2023
0123aae
resolve https://github.com/OpenAdaptAI/OpenAdapt/issues/441
Mustaballer Aug 2, 2023
7d0d343
add type annotation
Mustaballer Aug 3, 2023
b6cce11
Merge branch 'share-magic-wormhole' of https://github.com/Mustaballer…
Mustaballer Aug 3, 2023
d9eefa0
run black --preview and modify main.yml to check black --preview
Mustaballer Aug 3, 2023
961130b
remove unused import
Mustaballer Aug 4, 2023
5fd9868
Add timestamp to exported recording db files and update unit tests
Mustaballer Aug 4, 2023
453d2c2
Merge branch 'main' into share-magic-wormhole
Mustaballer Aug 10, 2023
4d66584
update poetry.lock
Mustaballer Aug 10, 2023
75b324d
Merge branch 'main' into share-magic-wormhole
Mustaballer Aug 10, 2023
dc5f2b6
fix: enhance publish action and authors in pyproject.toml
Mustaballer Aug 11, 2023
ebea2cb
modify release-and-publish.yml
Mustaballer Aug 11, 2023
c77f339
change author name to OpenAdapt.AI Team
Mustaballer Aug 11, 2023
8b1299d
Merge remote-tracking branch 'upstream/enhance-publishing' into share…
Mustaballer Aug 14, 2023
09dd4d6
test publish to test pypi
Mustaballer Aug 14, 2023
8ea0ee4
fix poetry conflicts and conflicting files
Mustaballer Aug 14, 2023
1b2eef3
Merge branch 'main' into share-magic-wormhole
Mustaballer Aug 18, 2023
0e8d620
resolve merge conflicts and linting errors from recent merge
Mustaballer Aug 18, 2023
5b9bbe0
Merge branch 'main' into share-magic-wormhole
Mustaballer Aug 28, 2023
24a3e79
Merge branch 'main' into share-magic-wormhole
Mustaballer Aug 29, 2023
a03bd59
update poetry.lock file and some formatting
Mustaballer Aug 29, 2023
a94c303
fix wormhole sharing
Mustaballer Sep 8, 2023
ca04296
Merge remote-tracking branch 'upstream/main' into share-magic-wormhole
Mustaballer Sep 8, 2023
61553f0
poetry lock
abrichr Nov 18, 2023
22b5f0c
fix failing tests
abrichr Nov 19, 2023
00c79cf
Merge branch 'main' into share-magic-wormhole
abrichr Nov 19, 2023
21340b2
poetry lock
abrichr Nov 19, 2023
0edcd88
add spacy-curated-transformers
abrichr Nov 19, 2023
1367cb9
remove custom visualize function
Mustaballer Dec 11, 2023
54a954f
change logging.py name to resolve naming conflict with logger library
Mustaballer Dec 11, 2023
9682209
Merge branch 'main' into share-magic-wormhole
Mustaballer Dec 11, 2023
6651f2c
Restore `export_recording` import with lint ignore comment
Mustaballer Dec 11, 2023
117694b
Merge branch 'share-magic-wormhole' of https://github.com/Mustaballer…
Mustaballer Dec 11, 2023
bf9e8bf
Merge branch 'main' into share-magic-wormhole
Mustaballer Dec 12, 2023
39b334e
Update openadapt/config.py
Mustaballer Dec 12, 2023
b79f3b5
Update openadapt/config.py
Mustaballer Dec 12, 2023
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OPENAI_API_KEY=<set your api key>
DB_FNAME=openadapt.db
37 changes: 33 additions & 4 deletions assets/fixtures.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,36 @@

-- Insert sample recordings
INSERT INTO recording (timestamp, monitor_width, monitor_height, double_click_interval_seconds, double_click_distance_pixels, platform, task_description)
VALUES
('2023-06-28 10:15:00', 1920, 1080, 0.5, 10, 'Windows', 'Task 1'),
('2023-06-29 14:30:00', 1366, 768, 0.3, 8, 'Mac', 'Task 2'),
('2023-06-30 09:45:00', 2560, 1440, 0.7, 12, 'Linux', 'Task 3');
VALUES
(1689889605.9053426, 1920, 1080, 0.5, 4, 'win32', 'type l');

-- Insert sample action_events
INSERT INTO action_event (name, timestamp, recording_timestamp, screenshot_timestamp, window_event_timestamp, mouse_x, mouse_y, mouse_dx, mouse_dy, mouse_button_name, mouse_pressed, key_name, key_char, key_vk, canonical_key_name, canonical_key_char, canonical_key_vk, parent_id, element_state)
VALUES
('press', 1690049582.7713714, 1689889605.9053426, 1690049582.7686925, 1690049556.2166219, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'l', '76', NULL, 'l', NULL, NULL, 'null'),
('release', 1690049582.826782, 1689889605.9053426, 1690049582.7686925, 1690049556.2166219, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'l', '76', NULL, 'l', NULL, NULL, 'null');

-- Insert sample screenshots
INSERT INTO screenshot (recording_timestamp, timestamp, png_data)
VALUES
(1689889605.9053426, 1690042711.774856, x'89504E470D0A1A0A0000000D49484452000000010000000108060000009077BF8A0000000A4944415408D7636000000005000000008D2B4233000000000049454E44AE426082');
-- PNG data represents a 1x1 pixel image with a white pixel

-- Insert sample window_events
INSERT INTO window_event (recording_timestamp, timestamp, state, title, left, top, width, height, window_id)
VALUES
(1689889605.9053426, 1690042703.7413292, '{"title": "recording.txt - openadapt - Visual Studio Code", "left": -9, "top": -9, "width": 1938, "height": 1048, "meta": {"class_name": "Chrome_WidgetWin_1", "control_id": 0, "rectangle": {"left": 0, "top": 0, "right": 1920, "bottom": 1030}, "is_visible": true, "is_enabled": true, "control_count": 0}}', 'recording.txt - openadapt - Visual Studio Code', -9, -9, 1938, 1048, '0');

-- Insert sample performance_stats
INSERT INTO performance_stat (recording_timestamp, event_type, start_time, end_time, window_id)
VALUES
(1689889605.9053426, 'action', 1690042703, 1690042711, 1),
(1689889605.9053426, 'action', 1690042712, 1690042720, 1);
-- Add more rows as needed for additional performance_stats

-- Insert sample memory_stats
INSERT INTO memory_stat (recording_timestamp, memory_usage_bytes, timestamp)
VALUES
(1689889605.9053426, 524288, 1690042703),
(1689889605.9053426, 1048576, 1690042711);
-- Add more rows as needed for additional memory_stats
14 changes: 12 additions & 2 deletions openadapt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import multiprocessing
import os
import pathlib
import shutil

from dotenv import load_dotenv
from loguru import logger
Expand Down Expand Up @@ -100,6 +101,13 @@
list(stop_str) for stop_str in STOP_STRS
] + SPECIAL_CHAR_STOP_SEQUENCES

ENV_FILE_PATH = ".env"
Mustaballer marked this conversation as resolved.
Show resolved Hide resolved
ENV_EXAMPLE_FILE_PATH = ".env.example"

# Create .env file if it doesn't exist
if not os.path.isfile(ENV_FILE_PATH):
shutil.copy(ENV_EXAMPLE_FILE_PATH, ENV_FILE_PATH)


def getenv_fallback(var_name: str) -> str:
"""Get the value of an environment variable or fallback to the default value.
Expand All @@ -108,7 +116,7 @@ def getenv_fallback(var_name: str) -> str:
var_name (str): The name of the environment variable.

Returns:
The value of the environment variable or the fallback default value.
str: The value of the environment variable or the default value if not found.

Raises:
ValueError: If the environment variable is not defined.
Expand All @@ -126,7 +134,7 @@ def getenv_fallback(var_name: str) -> str:
return rval


load_dotenv()
load_dotenv(ENV_FILE_PATH)

for key in _DEFAULTS:
val = getenv_fallback(key)
Expand All @@ -136,6 +144,8 @@ def getenv_fallback(var_name: str) -> str:
DB_FPATH = ROOT_DIRPATH / DB_FNAME # type: ignore # noqa
DB_URL = f"sqlite:///{DB_FPATH}"
DIRNAME_PERFORMANCE_PLOTS = "performance"
DATA_DIRECTORY_PATH = ROOT_DIRPATH / "data"
RECORDING_DIRECTORY_PATH = DATA_DIRECTORY_PATH / "recordings"


def obfuscate(val: str, pct_reveal: float = 0.1, char: str = "*") -> str:
Expand Down
9 changes: 9 additions & 0 deletions openadapt/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@ def get_latest_recording() -> Recording:
return db.query(Recording).order_by(sa.desc(Recording.timestamp)).limit(1).first()
Mustaballer marked this conversation as resolved.
Show resolved Hide resolved


def get_recording_by_id(recording_id: int) -> Recording:
"""Get the recording by an id.

Returns:
Recording: The latest recording object.
"""
return db.query(Recording).filter_by(id=recording_id).first()


def get_recording(timestamp: int) -> Recording:
"""Get a recording by timestamp.

Expand Down
146 changes: 143 additions & 3 deletions openadapt/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
Module: db.py
"""

from typing import Any
import os

from dictalchemy import DictableModel
from loguru import logger
from sqlalchemy import create_engine, event
from sqlalchemy.engine import reflection
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.schema import MetaData
import sqlalchemy as sa

from openadapt.config import DB_ECHO, DB_URL
from openadapt import config

NAMING_CONVENTION = {
"ix": "ix_%(column_0_label)s",
Expand Down Expand Up @@ -41,8 +47,8 @@ def __repr__(self) -> str:
def get_engine() -> sa.engine:
"""Create and return a database engine."""
engine = sa.create_engine(
DB_URL,
echo=DB_ECHO,
config.DB_URL,
echo=config.DB_ECHO,
)
return engine

Expand All @@ -68,3 +74,137 @@ def get_base(engine: sa.engine) -> sa.engine:
engine = get_engine()
Base = get_base(engine)
Session = sessionmaker(bind=engine)


def copy_recording_data(
source_engine: sa.engine,
target_engine: sa.engine,
recording_id: int,
exclude_tables: tuple = (),
) -> str:
"""Copy a specific recording from the source database to the target database.

Args:
source_engine (create_engine): SQLAlchemy engine for the source database.
target_engine (create_engine): SQLAlchemy engine for the target database.
recording_id (int): The ID of the recording to copy.
exclude_tables (tuple, optional): Tables excluded from copying. Defaults to ().

Returns:
str: The URL or path of the target database.
"""
try:
with source_engine.connect() as src_conn, target_engine.connect() as tgt_conn:
src_metadata = MetaData()
tgt_metadata = MetaData()

@event.listens_for(src_metadata, "column_reflect")
def genericize_datatypes(
inspector: reflection.Inspector,
tablename: str,
column_dict: dict[str, Any],
) -> None:
column_dict["type"] = column_dict["type"].as_generic(
allow_nulltype=True
)

tgt_metadata.reflect(bind=target_engine)
src_metadata.reflect(bind=source_engine)

# Drop all tables in target database (except excluded tables)
for table in reversed(tgt_metadata.sorted_tables):
if table.name not in exclude_tables:
logger.info("Dropping table =", table.name)
table.drop(bind=target_engine)

tgt_metadata.clear()
tgt_metadata.reflect(bind=target_engine)
src_metadata.reflect(bind=source_engine)

# Create all tables in target database (except excluded tables)
for table in src_metadata.sorted_tables:
if table.name not in exclude_tables:
table.create(bind=target_engine)

# Refresh metadata before copying data
tgt_metadata.clear()
tgt_metadata.reflect(bind=target_engine)

# Get the source recording table
src_recording_table = src_metadata.tables["recording"]
tgt_recording_table = tgt_metadata.tables["recording"]

# Select the recording with the given recording_id from the source
src_select = src_recording_table.select().where(
src_recording_table.c.id == recording_id
)
src_recording = src_conn.execute(src_select).fetchone()

# Insert the recording into the target recording table
tgt_conn.execute(tgt_recording_table.insert().values(src_recording))

# Get the timestamp from the source recording
src_timestamp = src_recording["timestamp"]

# Copy data from tables with the same timestamp
for table in src_metadata.sorted_tables:
if (
table.name not in exclude_tables
and "recording_timestamp" in table.columns.keys()
):
# Select data from source table with the same timestamp
src_select = table.select().where(
table.c.recording_timestamp == src_timestamp
)
src_rows = src_conn.execute(src_select).fetchall()

# Insert data into target table
tgt_table = tgt_metadata.tables[table.name]
for row in src_rows:
tgt_insert = tgt_table.insert().values(**row._asdict())
tgt_conn.execute(tgt_insert)

# Copy data from alembic_version table
src_alembic_version_table = src_metadata.tables["alembic_version"]
tgt_alembic_version_table = tgt_metadata.tables["alembic_version"]
src_alembic_version_select = src_alembic_version_table.select()
src_alembic_version_data = src_conn.execute(
src_alembic_version_select
).fetchall()
for row in src_alembic_version_data:
tgt_alembic_version_insert = tgt_alembic_version_table.insert().values(
row
)
tgt_conn.execute(tgt_alembic_version_insert)

# Commit the transaction
tgt_conn.commit()

except Exception as exc:
# Perform cleanup
db_file_path = target_engine.url.database
if db_file_path and os.path.exists(db_file_path):
os.remove(db_file_path)
logger.exception(exc)
return ""

return target_engine.url.database


def export_recording(recording_id: int) -> str:
"""Export a recording by its ID to a new SQLite database.

Args:
recording_id (int): The ID of the recording to export.

Returns:
str: The file path of the new database.
"""
db_fname = f"recording_{recording_id}.db"
target_path = config.ROOT_DIRPATH / db_fname
target_db_url = f"sqlite:///{target_path}"

target_engine = create_engine(target_db_url, future=True)

db_file_path = copy_recording_data(engine, target_engine, recording_id)
return db_file_path
Loading