diff --git a/README.md b/README.md index 07d4a39..29c0631 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,8 @@ org.freedesktop.tuhi1.Device or we run out of memory, whichever happens earlier. Use GetJSONData() to retrieve the data from the daemon. + DO NOT RELY ON THE DAEMON FOR PERMANENT STORAGE + When drawings become available from the device, the DrawingsAvailable property updates to the number of available drawings. When the button is pressed multiple times, any new data is appended diff --git a/tuhi/base.py b/tuhi/base.py index b5bb836..8d0f06b 100644 --- a/tuhi/base.py +++ b/tuhi/base.py @@ -45,7 +45,6 @@ class TuhiDevice(GObject.Object): GObject.Object.__init__(self) self.config = config self._wacom_device = None - self.drawings = [] # We need either uuid or paired as false assert uuid is not None or paired is False self.paired = paired @@ -84,6 +83,12 @@ class TuhiDevice(GObject.Object): self._tuhi_dbus_device.connect('pair-requested', self._on_pair_requested) self._tuhi_dbus_device.connect('notify::listening', self._on_listening_updated) + drawings = self.config.load_drawings(self.address) + if drawings: + logger.debug(f'{self.address}: loaded {len(drawings)} drawings from disk') + for d in drawings: + self._tuhi_dbus_device.add_drawing(d) + @GObject.Property def listening(self): return self._tuhi_dbus_device.listening @@ -115,6 +120,7 @@ class TuhiDevice(GObject.Object): def _on_drawing_received(self, device, drawing): logger.debug('Drawing received') self._tuhi_dbus_device.add_drawing(drawing) + self.config.store_drawing(self.address, drawing) def _on_fetching_finished(self, device, exception, bluez_device): bluez_device.disconnect_device() diff --git a/tuhi/config.py b/tuhi/config.py index c5fe020..4db61f5 100644 --- a/tuhi/config.py +++ b/tuhi/config.py @@ -18,6 +18,7 @@ import os import configparser import re import logging +from .drawing import Drawing logger = logging.getLogger('tuhi.config') @@ -97,3 +98,38 @@ class TuhiConfig(GObject.Object): config = configparser.ConfigParser() config.read(path) self._devices[address] = config['Device'] + + def store_drawing(self, address, drawing): + assert is_btaddr(address) + assert drawing is not None + + if address not in self.devices: + logger.error("{}: cannot store drawings for unknown device".format(address)) + return + + logger.debug("{}: adding new drawing, timestamp {}".format(address, drawing.timestamp)) + path = os.path.join(ROOT_PATH, address, "{}.json".format(drawing.timestamp)) + + with open(path, "w") as f: + f.write(drawing.to_json()) + + def load_drawings(self, address): + assert is_btaddr(address) + + drawings = [] + if address not in self.devices: + return drawings + + configdir = os.path.join(ROOT_PATH, address) + with os.scandir(configdir) as it: + for entry in it: + if not entry.is_file(): + continue + + if not entry.name.endswith('.json'): + continue + + d = Drawing.from_json(entry) + drawings.append(d) + + return drawings diff --git a/tuhi/drawing.py b/tuhi/drawing.py index f4fcd84..e13836d 100644 --- a/tuhi/drawing.py +++ b/tuhi/drawing.py @@ -13,6 +13,9 @@ from gi.repository import GObject import json +import logging + +logger = logging.getLogger('tuhi.drawing') class Point(GObject.Object): @@ -73,6 +76,8 @@ class Drawing(GObject.Object): Abstracts a drawing. The drawing is composed Strokes, each of which has Points. """ + JSON_FILE_FORMAT_VERSION = 1 + def __init__(self, name, dimensions, timestamp): GObject.Object.__init__(self) self.name = name @@ -97,13 +102,41 @@ class Drawing(GObject.Object): return l def to_json(self): - JSON_FILE_FORMAT_VERSION = 1 - json_data = { - 'version': JSON_FILE_FORMAT_VERSION, + 'version': self.JSON_FILE_FORMAT_VERSION, 'devicename': self.name, 'dimensions': list(self.dimensions), 'timestamp': self.timestamp, 'strokes': [s.to_dict() for s in self.strokes] } return json.dumps(json_data) + + @classmethod + def from_json(cls, path): + d = None + with open(path, 'r') as fp: + json_data = json.load(fp) + + try: + if json_data['version'] != cls.JSON_FILE_FORMAT_VERSION: + logger.error(f'{path}: Invalid file format version') + return d + + name = json_data['devicename'] + dimensions = tuple(json_data['dimensions']) + timestamp = json_data['timestamp'] + d = Drawing(name, dimensions, timestamp) + + for s in json_data['strokes']: + stroke = d.new_stroke() + for p in s['points']: + position = p.get('position', None) + pressure = p.get('pressure', None) + stroke.new_abs(position, pressure) + except KeyError: + logger.error(f'{path}: failed to parse json file') + + return d + + def __repr__(self): + return f'Drawing from {self.name} at {self.timestamp}, {len(self.strokes)} strokes'