From 24e317f8f3444647083f3914cab1281597217cc2 Mon Sep 17 00:00:00 2001 From: Ayush Date: Fri, 1 Oct 2021 16:36:33 +0530 Subject: [PATCH 1/3] feat: added basic settings sync to server --- aw_core/models.py | 50 +++++++++++++++++++++++++- aw_core/schemas/settings.json | 15 ++++++++ aw_datastore/datastore.py | 10 +++++- aw_datastore/storages/abstract.py | 10 +++++- aw_datastore/storages/memory.py | 8 +++++ aw_datastore/storages/mongodb.py | 8 +++++ aw_datastore/storages/peewee.py | 58 ++++++++++++++++++++++++++++++- aw_datastore/storages/sqlite.py | 31 ++++++++++++++++- 8 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 aw_core/schemas/settings.json diff --git a/aw_core/models.py b/aw_core/models.py index 9fb9344f..739f3ae5 100644 --- a/aw_core/models.py +++ b/aw_core/models.py @@ -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 @@ -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 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 + + diff --git a/aw_core/schemas/settings.json b/aw_core/schemas/settings.json new file mode 100644 index 00000000..ec5791df --- /dev/null +++ b/aw_core/schemas/settings.json @@ -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" + } + } +} diff --git a/aw_datastore/datastore.py b/aw_datastore/datastore.py index 9e7ebae3..37267246 100644 --- a/aw_datastore/datastore.py +++ b/aw_datastore/datastore.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone, timedelta from typing import Dict, List, Union, Callable, Optional -from aw_core.models import Event +from aw_core.models import Event, Setting from .storages import AbstractStorage @@ -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: @@ -174,3 +180,5 @@ def replace_last(self, event): def replace(self, event_id, event): return self.ds.storage_strategy.replace(self.bucket_id, event_id, event) + +# class Setting: \ No newline at end of file diff --git a/aw_datastore/storages/abstract.py b/aw_datastore/storages/abstract.py index 6755c0d9..fc8b6924 100644 --- a/aw_datastore/storages/abstract.py +++ b/aw_datastore/storages/abstract.py @@ -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): @@ -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 \ No newline at end of file diff --git a/aw_datastore/storages/memory.py b/aw_datastore/storages/memory.py index 08b58aab..7e2049fa 100644 --- a/aw_datastore/storages/memory.py +++ b/aw_datastore/storages/memory.py @@ -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 diff --git a/aw_datastore/storages/mongodb.py b/aw_datastore/storages/mongodb.py index 3c276557..6cafd273 100644 --- a/aw_datastore/storages/mongodb.py +++ b/aw_datastore/storages/mongodb.py @@ -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 \ No newline at end of file diff --git a/aw_datastore/storages/peewee.py b/aw_datastore/storages/peewee.py index 6f93ca80..876d57fb 100644 --- a/aw_datastore/storages/peewee.py +++ b/aw_datastore/storages/peewee.py @@ -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 @@ -106,6 +106,16 @@ def json(self): "data": json.loads(self.datastr), } +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" @@ -130,6 +140,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: @@ -327,3 +338,48 @@ 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} + ) + ) + + res = q.execute() + return True + diff --git a/aw_datastore/storages/sqlite.py b/aw_datastore/storages/sqlite.py index 6029bad0..b2ad4257 100644 --- a/aw_datastore/storages/sqlite.py +++ b/aw_datastore/storages/sqlite.py @@ -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 @@ -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); """ @@ -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) @@ -303,3 +315,20 @@ 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 From 799c9acab1cad21e27d12f235cea939291a8da55 Mon Sep 17 00:00:00 2001 From: Ayush Date: Fri, 1 Oct 2021 16:51:36 +0530 Subject: [PATCH 2/3] style: fix lint errors --- aw_core/models.py | 3 --- aw_datastore/datastore.py | 4 +--- aw_datastore/storages/abstract.py | 2 +- aw_datastore/storages/memory.py | 6 +++--- aw_datastore/storages/mongodb.py | 6 +++--- aw_datastore/storages/peewee.py | 11 +++++------ aw_datastore/storages/sqlite.py | 4 +--- 7 files changed, 14 insertions(+), 22 deletions(-) diff --git a/aw_core/models.py b/aw_core/models.py index 739f3ae5..616e05f4 100644 --- a/aw_core/models.py +++ b/aw_core/models.py @@ -154,7 +154,6 @@ def __init__( self.key = key self.value = value - 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.""" @@ -185,5 +184,3 @@ def value(self) -> dict: @value.setter def value(self, value: Text) -> None: self["value"] = value - - diff --git a/aw_datastore/datastore.py b/aw_datastore/datastore.py index 37267246..1c60e8ef 100644 --- a/aw_datastore/datastore.py +++ b/aw_datastore/datastore.py @@ -69,7 +69,7 @@ def buckets(self): def get_settings(self): return self.storage_strategy.get_settings() - + def update_setting(self, key, value): return self.storage_strategy.update_setting(key, value) @@ -180,5 +180,3 @@ def replace_last(self, event): def replace(self, event_id, event): return self.ds.storage_strategy.replace(self.bucket_id, event_id, event) - -# class Setting: \ No newline at end of file diff --git a/aw_datastore/storages/abstract.py b/aw_datastore/storages/abstract.py index fc8b6924..57994954 100644 --- a/aw_datastore/storages/abstract.py +++ b/aw_datastore/storages/abstract.py @@ -86,4 +86,4 @@ def get_settings(self) -> List[Setting]: @abstractmethod def update_setting(self, key: str, value: str) -> bool: - raise NotImplementedError \ No newline at end of file + raise NotImplementedError diff --git a/aw_datastore/storages/memory.py b/aw_datastore/storages/memory.py index 7e2049fa..af2f09a2 100644 --- a/aw_datastore/storages/memory.py +++ b/aw_datastore/storages/memory.py @@ -116,9 +116,9 @@ def replace_last(self, bucket_id, event): self.db[bucket_id][-1] = event def get_settings(self): - settings = self.db['settings'] - return settings; + settings = self.db["settings"] + return settings def update_setting(self, key: str, value: str): - self.db['settings'][key] = value + self.db["settings"][key] = value return True diff --git a/aw_datastore/storages/mongodb.py b/aw_datastore/storages/mongodb.py index 6cafd273..54a56835 100644 --- a/aw_datastore/storages/mongodb.py +++ b/aw_datastore/storages/mongodb.py @@ -168,9 +168,9 @@ def replace(self, bucket_id: str, event_id, event: Event) -> bool: return True def get_settings(self): - settings = self.db['settings'].find() + 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 \ No newline at end of file + self.db["settings"].update({"key": key}, {"value": value}, True ) + return True diff --git a/aw_datastore/storages/peewee.py b/aw_datastore/storages/peewee.py index 876d57fb..605e6ceb 100644 --- a/aw_datastore/storages/peewee.py +++ b/aw_datastore/storages/peewee.py @@ -106,6 +106,7 @@ def json(self): "data": json.loads(self.datastr), } + class SettingModel(BaseModel): id = AutoField() key = CharField(unique=True) @@ -117,6 +118,7 @@ def json(self): "value": self.value, } + class PeeweeStorage(AbstractStorage): sid = "peewee" @@ -350,9 +352,7 @@ def get_settings( SELECT * FROM settings """ - q = ( - SettingModel.select() - ) + q = (SettingModel.select() res = q.execute() settings = [Setting(**e) for e in list(map(SettingModel.json, res))] @@ -370,15 +370,14 @@ def update_setting(self, key, value): WHERE key = ? """ - q = ( - SettingModel + q = SettingModel .insert(value= value, key= key) .on_conflict( conflict_target=[SettingModel.key], preserve=[SettingModel.key], update={SettingModel.value: value} ) - ) + res = q.execute() return True diff --git a/aw_datastore/storages/sqlite.py b/aw_datastore/storages/sqlite.py index b2ad4257..a8888674 100644 --- a/aw_datastore/storages/sqlite.py +++ b/aw_datastore/storages/sqlite.py @@ -319,9 +319,7 @@ def get_eventcount( def get_settings(self): self.commit() c = self.conn.cursor - query = ( - "SELECT * FROM settings" - ) + query = "SELECT * FROM settings" rows = c.execute(query) return rows From e8610d8894137d1fd96a4627d77b8589651ad4a4 Mon Sep 17 00:00:00 2001 From: Ayush Date: Fri, 1 Oct 2021 16:58:54 +0530 Subject: [PATCH 3/3] style: fix lgtm errors --- aw_core/models.py | 3 +++ aw_datastore/datastore.py | 2 +- aw_datastore/storages/peewee.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/aw_core/models.py b/aw_core/models.py index 616e05f4..d85d0ae8 100644 --- a/aw_core/models.py +++ b/aw_core/models.py @@ -154,6 +154,9 @@ def __init__( 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.""" diff --git a/aw_datastore/datastore.py b/aw_datastore/datastore.py index 1c60e8ef..9792a676 100644 --- a/aw_datastore/datastore.py +++ b/aw_datastore/datastore.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone, timedelta from typing import Dict, List, Union, Callable, Optional -from aw_core.models import Event, Setting +from aw_core.models import Event from .storages import AbstractStorage diff --git a/aw_datastore/storages/peewee.py b/aw_datastore/storages/peewee.py index 605e6ceb..83efe9d6 100644 --- a/aw_datastore/storages/peewee.py +++ b/aw_datastore/storages/peewee.py @@ -379,6 +379,6 @@ def update_setting(self, key, value): ) - res = q.execute() + q.execute() return True