diff --git a/dstat_interface/db.py b/dstat_interface/db.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e1fda80e3c69ec7b85a85162fa412c663ad97b4
--- /dev/null
+++ b/dstat_interface/db.py
@@ -0,0 +1,194 @@
+import logging
+from time import sleep, time
+from os.path import expanduser
+from uuid import uuid4
+
+from BTrees.OOBTree import OOBTree
+
+import mr_db.mr_db as db
+
+logger = logging.getLogger("dstat.db")
+
+current_db = None
+
+def start_db(path=None):
+ global current_db
+ current_db = Database(data_dir=path)
+
+def restart_db(object, path):
+ logger.info("Restarting database")
+ global current_db
+ if current_db is None:
+ logger.info("No database running")
+ start_db(path=path)
+ else:
+ stop_db()
+ start_db(path=path)
+
+def stop_db():
+ global current_db
+ if not current_db is None:
+ logger.info("Stopping ZEO")
+ current_db.disconnect()
+ current_db = None
+ db.stop_server()
+ else:
+ logger.warning("Tried to disconnect ZEO when not connected")
+
+class Database(object):
+ def __init__(self, name='dstat', data_dir=None):
+ if data_dir is None:
+ data_dir = expanduser('~/.mr_db')
+ self.connected = False
+ self.name = name
+
+ self.db_connect(data_dir)
+
+ self.db = self.connection.databases
+
+ # Make sure database exists
+ if not self.db.has_key(name):
+ self.db[name] = OOBTree()
+ db.transaction.commit()
+
+ def disconnect(self):
+ if self.connected is True:
+ self.connection.db.close()
+ else:
+ logger.war("Tried to disconnect ZEO when not connected")
+
+ def db_connect(self, root_dir):
+ """Connects to ZEO process. Starts ZEO if not running. Returns
+ connection object.
+ """
+ if root_dir == '':
+ root_dir = None
+
+ while self.connected is False:
+ try:
+ self.connection = db.DbConnection(root_dir=root_dir)
+ self.connected = True
+ logger.info("Connected to ZEO server")
+ except db.ClientStorage.ClientDisconnected:
+ db.stop_server()
+ logger.info("Starting ZEO server -- root_dir = %s", root_dir)
+ db_proc = db.start_server(root_dir=root_dir)
+ sleep(3)
+
+ def add_results(self, measurement_uuid=None, measurement_name=None,
+ experiment_uuid=None, experiment_metadata=None,
+ patient_id=None,
+ timestamp=None,
+ data=None):
+
+ """Add a measurement"""
+
+ if experiment_metadata is None:
+ experiment_metadata = {}
+
+ try:
+ logger.info("Starting DB transaction")
+ db.transaction.begin()
+
+ logger.info("Creating Experiment with id: %s", experiment_uuid)
+ exp_db, exp_id = self.add_experiment(
+ experiment_uuid=experiment_uuid,
+ timestamp=timestamp,
+ **experiment_metadata)
+
+ logger.info("Adding Measurement with id: %s", measurement_uuid)
+ name = self.add_dstat_measurement(experiment=exp_db[exp_id],
+ measurement_uuid=measurement_uuid,
+ name=measurement_name,
+ timestamp=timestamp,
+ data=data)
+
+ if patient_id is not None:
+ if not patient_id in self.db['patients']:
+ logger.info("Creating patient with id: %s", patient_id)
+ patient = db.Patient(pid=patient_id)
+ self.db['patients'][patient_id] = patient
+
+ if not exp_id in self.db['patients'][patient_id].experiments:
+ logger.info("Linking experiment into patient with id: %s",
+ patient_id)
+ self.db['patients'][patient_id].link_experiment(exp_db,
+ exp_id)
+
+ logger.info("Committing DB transaction")
+ db.transaction.commit()
+
+ return name
+
+ except:
+ logger.error("Aborting DB transaction")
+ db.transaction.abort()
+ raise
+
+ def add_experiment(self, experiment_uuid=None, timestamp=None, **kwargs):
+ """Add a new experiment. Will raise KeyExistsError if id is already
+ in db to avoid unintended collisions.
+ ----
+ Arguments:
+ id: experiment id---UUID will be generated if not supplied
+ timestamp: current time---Will be generated if not supplied
+ kwargs: additional keyword arguments that will be saved in experiment.
+ """
+
+ if experiment_uuid is None:
+ experiment_uuid = uuid4().hex
+ if timestamp is None:
+ timestamp = time()
+
+ kwargs.update({'id':experiment_uuid, 'timestamp':timestamp})
+
+ if not experiment_uuid in self.db[self.name]:
+ self.db[self.name][experiment_uuid] = db.PersistentMapping()
+ else:
+ logger.info("Experiment already exists, appending")
+
+ self.db[self.name][experiment_uuid].update(kwargs)
+
+ return (self.db[self.name], experiment_uuid)
+
+ def add_dstat_measurement(self, experiment, measurement_uuid=None,
+ data=None, timestamp=None, name=None):
+
+ if measurement_uuid is None:
+ measurement_uuid = uuid4().hex
+ if timestamp is None:
+ timestamp = time()
+ if data is None:
+ data = {}
+
+ if not 'measurements' in experiment:
+ experiment['measurements'] = db.PersistentMapping()
+ if not 'measurements_by_name' in experiment:
+ experiment['measurements_by_name'] = db.PersistentMapping()
+
+ if measurement_uuid in experiment['measurements']:
+ raise db.KeyExistsError(measurement_uuid,
+ "Measurement ID already exists. Access directly to update")
+
+ data['timestamp'] = timestamp
+ data['id'] = measurement_uuid
+
+ if name is not None:
+ data['name'] = name
+
+ experiment['measurements'][measurement_uuid] = data
+
+ if name is not None:
+ while name in experiment['measurements_by_name']:
+ first, sep, last = name.rpartition('-')
+
+ if last.isdigit():
+ index = int(last)
+ index += 1
+ name = sep.join((first,str(index)))
+ else:
+ name += '-1'
+
+ experiment['measurements_by_name'][name] = data
+
+ return name
\ No newline at end of file
diff --git a/dstat_interface/interface/db.glade b/dstat_interface/interface/db.glade
new file mode 100644
index 0000000000000000000000000000000000000000..20a833aeea2b6f2974bc7fbaa4a0b10b385d0881
--- /dev/null
+++ b/dstat_interface/interface/db.glade
@@ -0,0 +1,219 @@
+
+
+
+
+
+
diff --git a/dstat_interface/interface/db.py b/dstat_interface/interface/db.py
new file mode 100755
index 0000000000000000000000000000000000000000..94125c0bc490ca298ac3a9bacc86f3af77c914e8
--- /dev/null
+++ b/dstat_interface/interface/db.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+# DStat Interface - An interface for the open hardware DStat potentiostat
+# Copyright (C) 2014 Michael D. M. Dryden -
+# Wheeler Microfluidics Laboratory
+#
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import os
+import sys
+import logging
+from uuid import uuid4
+
+import gtk
+import gobject
+
+logger = logging.getLogger('dstat.interface.db')
+
+class DB_Window(gobject.GObject):
+ __gsignals__ = {
+ 'db-reset' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_STRING,)
+ )
+ }
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self.builder = gtk.Builder()
+ self.builder.add_from_file('interface/db.glade')
+ self.builder.connect_signals(self)
+
+ self.window = self.builder.get_object('db_window')
+ # Make sure window isn't destroyed if checkbox hit.
+ self.window.connect('delete-event', self.on_delete_event)
+ self.db_control_table = self.builder.get_object('db_control_table')
+
+ ui_keys = ['exp_id_entry',
+ 'measure_id_entry',
+ 'patient_id_entry',
+ 'measure_name_entry',
+ 'db_path_entry',
+ 'db_apply_button',
+ 'exp_id_autogen_button',
+ 'db_enable_checkbutton'
+ ]
+ self.persistent_keys = ['db_path_entry','db_enable_checkbutton']
+ self.ui = {}
+ for i in ui_keys:
+ self.ui[i] = self.builder.get_object(i)
+
+ self.metadata_map = [('experiment_uuid', 'exp_id_entry'),
+ ('patient_id', 'patient_id_entry'),
+ ('name', 'measure_name_entry')]
+
+ self._params = {}
+
+ @property
+ def persistent_params(self):
+ """Dict of parameters that should be saved."""
+ self._get_params()
+ return {k:self._params[k] for k in self.persistent_keys}
+
+ @property
+ def params(self):
+ """Dict of parameters."""
+ logger.info("getter")
+ self._get_params()
+ logger.info("params: %s", self._params)
+ return self._params
+
+ def _get_params(self):
+ """Updates self._params from UI."""
+ for i in self.ui:
+ if i.endswith('checkbutton'):
+ self._params[i] = self.ui[i].get_active()
+ elif i.endswith('entry'):
+ text = self.ui[i].get_text()
+ logger.info("%s=%s",i,text)
+ if text == "":
+ text = None
+ self._params[i] = text
+
+ @params.setter
+ def params(self, params):
+ logger.info("Setter Params: %s", params)
+ if self._params is {}:
+ self._params = dict.fromkeys(self.ui.keys())
+
+ for i in self._params:
+ if i in params:
+ self._params[i] = params[i]
+ self._set_params()
+ logger.info("params: %s", self._params)
+
+ def _set_params(self):
+ """Updates UI with new parameters."""
+ for i in self.ui:
+ if i.endswith('checkbutton'):
+ self.ui[i].set_active(self._params[i])
+ elif i.endswith('entry'):
+ if self._params[i] is not None:
+ self.ui[i].set_text(self._params[i])
+ else:
+ self.ui[i].set_text("")
+
+ def update_from_metadata(self, metadata):
+ params = {k : metadata[j] for j, k in self.metadata_map}
+ # if j in metadata}
+ self.params = params
+
+ def show(self):
+ self.window.show_all()
+ self.on_db_enable_checkbutton_toggled()
+
+ def on_db_enable_checkbutton_toggled(self, *args):
+ if self.ui['db_enable_checkbutton'].get_active():
+ self.db_control_table.show()
+ else:
+ self.db_control_table.hide()
+
+ def on_exp_id_autogen_button_clicked(self, *args):
+ self.ui['exp_id_entry'].set_text(uuid4().hex)
+
+ def on_db_apply_button_clicked(self, widget=None, *data):
+ self.emit('db-reset', self.params['db_path_entry'])
+
+ def on_delete_event(self, widget=None, *data):
+ widget.hide()
+ return True
+
+gobject.type_register(DB_Window)
\ No newline at end of file