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: added basic settings sync to server #106

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
50 changes: 49 additions & 1 deletion aw_core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numbers
import typing
from datetime import datetime, timedelta, timezone
from typing import Any, List, Dict, Union, Optional
from typing import Any, List, Dict, Union, Optional, Text

import iso8601

Expand Down Expand Up @@ -139,3 +139,51 @@ def duration(self, duration: Duration) -> None:
raise TypeError(
"Couldn't parse duration of invalid type {}".format(type(duration))
)


class Setting(dict):
"""
Used to represents a setting.
"""

def __init__(
self,
key: Text = None,
value: Text = None,
) -> None:
self.key = key
self.value = value

def __eq__(self, other: object) -> bool:
return True

def to_json_dict(self) -> dict:
"""Useful when sending data over the wire.
Any mongodb interop should not use do this as it accepts datetimes."""
json_data = self.copy()
return json_data

def to_json_str(self) -> str:
data = self.to_json_dict()
return json.dumps(data)

def _hasprop(self, propname: str) -> bool:
"""Badly named, but basically checks if the underlying
dict has a prop, and if it is a non-empty list"""
return propname in self and self[propname] is not None

@property
def key(self) -> Text:
return self["key"] if self._hasprop("key") else None

@key.setter
def key(self, key: Text) -> None:
self["key"] = key

@property
def value(self) -> dict:
return self["value"] if self._hasprop("value") else None

@value.setter
def value(self, value: Text) -> None:
self["value"] = value
15 changes: 15 additions & 0 deletions aw_core/schemas/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Settings",
"description": "The Settings model that is used in ActivityWatch",
"type": "object",
"required": ["key", "value"],
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
6 changes: 6 additions & 0 deletions aw_datastore/datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ def delete_bucket(self, bucket_id: str):
def buckets(self):
return self.storage_strategy.buckets()

def get_settings(self):
return self.storage_strategy.get_settings()

def update_setting(self, key, value):
return self.storage_strategy.update_setting(key, value)


class Bucket:
def __init__(self, datastore: Datastore, bucket_id: str) -> None:
Expand Down
10 changes: 9 additions & 1 deletion aw_datastore/storages/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import datetime
from abc import ABCMeta, abstractmethod, abstractproperty

from aw_core.models import Event
from aw_core.models import Event, Setting


class AbstractStorage(metaclass=ABCMeta):
Expand Down Expand Up @@ -79,3 +79,11 @@ def replace(self, bucket_id: str, event_id: int, event: Event) -> bool:
@abstractmethod
def replace_last(self, bucket_id: str, event: Event) -> None:
raise NotImplementedError

@abstractmethod
def get_settings(self) -> List[Setting]:
raise NotImplementedError

@abstractmethod
def update_setting(self, key: str, value: str) -> bool:
raise NotImplementedError
8 changes: 8 additions & 0 deletions aw_datastore/storages/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,11 @@ def replace(self, bucket_id, event_id, event):

def replace_last(self, bucket_id, event):
self.db[bucket_id][-1] = event

def get_settings(self):
settings = self.db["settings"]
return settings

def update_setting(self, key: str, value: str):
self.db["settings"][key] = value
return True
8 changes: 8 additions & 0 deletions aw_datastore/storages/mongodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,11 @@ def replace(self, bucket_id: str, event_id, event: Event) -> bool:
)
event.id = event_id
return True

def get_settings(self):
settings = self.db["settings"].find()
return settings

def update_setting(self, key: str, value: str):
self.db["settings"].update({"key": key}, {"value": value}, True )
return True
57 changes: 56 additions & 1 deletion aw_datastore/storages/peewee.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)
from playhouse.sqlite_ext import SqliteExtDatabase

from aw_core.models import Event
from aw_core.models import Event, Setting
from aw_core.dirs import get_data_dir

from .abstract import AbstractStorage
Expand Down Expand Up @@ -107,6 +107,18 @@ def json(self):
}


class SettingModel(BaseModel):
id = AutoField()
key = CharField(unique=True)
value = CharField()

def json(self):
return {
"key": self.key,
"value": self.value,
}


class PeeweeStorage(AbstractStorage):
sid = "peewee"

Expand All @@ -130,6 +142,7 @@ def __init__(self, testing: bool = True, filepath: str = None) -> None:
self.bucket_keys: Dict[str, int] = {}
BucketModel.create_table(safe=True)
EventModel.create_table(safe=True)
SettingModel.create_table(safe=True)
self.update_bucket_keys()

def update_bucket_keys(self) -> None:
Expand Down Expand Up @@ -327,3 +340,45 @@ def _where_range(
q = q.where(EventModel.timestamp <= endtime)

return q

def get_settings(
self,
):
"""
Fetch all settings

Example raw query:

SELECT * FROM settings

"""
q = (SettingModel.select()

res = q.execute()
settings = [Setting(**e) for e in list(map(SettingModel.json, res))]

return settings

def update_setting(self, key, value):
"""
Update a setting

Example raw query:

UPDATE settings
SET value = ?
WHERE key = ?

"""
q = SettingModel
.insert(value= value, key= key)
.on_conflict(
conflict_target=[SettingModel.key],
preserve=[SettingModel.key],
update={SettingModel.value: value}
)


q.execute()
return True

29 changes: 28 additions & 1 deletion aw_datastore/storages/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@

logger = logging.getLogger(__name__)

LATEST_VERSION = 1
"""
v1: initial version
v2: add settings table
"""
LATEST_VERSION = 2

# The max integer value in SQLite is signed 8 Bytes / 64 bits
MAX_TIMESTAMP = 2 ** 63 - 1
Expand Down Expand Up @@ -42,6 +46,13 @@
)
"""

CREATE_SETTINGS_TABLE = """
CREATE TABLE IF NOT EXISTS settings (
key TEXT NOT NULL,
value TEXT NOT NULL
)
"""

INDEX_BUCKETS_TABLE_ID = """
CREATE INDEX IF NOT EXISTS event_index_id ON events(id);
"""
Expand Down Expand Up @@ -77,6 +88,7 @@ def __init__(self, testing, filepath: str = None, enable_lazy_commit=True) -> No
# Create tables
self.conn.execute(CREATE_BUCKETS_TABLE)
self.conn.execute(CREATE_EVENTS_TABLE)
self.conn.execute(CREATE_SETTINGS_TABLE)
self.conn.execute(INDEX_BUCKETS_TABLE_ID)
self.conn.execute(INDEX_EVENTS_TABLE_STARTTIME)
self.conn.execute(INDEX_EVENTS_TABLE_ENDTIME)
Expand Down Expand Up @@ -303,3 +315,18 @@ def get_eventcount(
row = rows.fetchone()
eventcount = row[0]
return eventcount

def get_settings(self):
self.commit()
c = self.conn.cursor
query = "SELECT * FROM settings"
rows = c.execute(query)
return rows

def update_setting(self, key, value):
query = """UPDATE settings
SET value = ?
WHERE key = ?"""
self.conn.execute(query, [key, value])
self.conditional_commit(1)
return True