diff --git a/python/obitools3/dms/capi/obidms.pxd b/python/obitools3/dms/capi/obidms.pxd index 905064e..e7da3c1 100644 --- a/python/obitools3/dms/capi/obidms.pxd +++ b/python/obitools3/dms/capi/obidms.pxd @@ -4,16 +4,30 @@ from .obitypes cimport const_char_p, \ obiversion_t cdef extern from "obidms.h" nogil: + + struct OBIDMS_infos_t : + bint little_endian + size_t file_size + size_t used_size + const_char_p comments + + ctypedef OBIDMS_infos_t* OBIDMS_infos_p + + struct OBIDMS_t: - const_char_p dms_name + const_char_p dms_name + OBIDMS_infos_p infos ctypedef OBIDMS_t* OBIDMS_p + OBIDMS_p obi_dms(const_char_p dms_name) OBIDMS_p obi_open_dms(const_char_p dms_path) OBIDMS_p obi_test_open_dms(const_char_p dms_path) OBIDMS_p obi_create_dms(const_char_p dms_path) int obi_dms_exists(const char* dms_path) + int obi_dms_write_comments(OBIDMS_p dms, const char* comments) + int obi_dms_add_comment(OBIDMS_p dms, const char* key, const char* value) int obi_close_dms(OBIDMS_p dms, bint force) char* obi_dms_get_dms_path(OBIDMS_p dms) char* obi_dms_get_full_path(OBIDMS_p dms, const_char_p path_name) diff --git a/python/obitools3/dms/dms.pxd b/python/obitools3/dms/dms.pxd index 7de427c..6fc3068 100644 --- a/python/obitools3/dms/dms.pxd +++ b/python/obitools3/dms/dms.pxd @@ -9,3 +9,7 @@ cdef class DMS(OBIWrapper): cdef inline OBIDMS_p pointer(self) cpdef int view_count(self) + + +cdef class DMS_comments(dict): + cdef DMS _dms diff --git a/python/obitools3/dms/dms.pyx b/python/obitools3/dms/dms.pyx index a0b33e0..d8beb33 100644 --- a/python/obitools3/dms/dms.pyx +++ b/python/obitools3/dms/dms.pyx @@ -9,14 +9,18 @@ from .capi.obidms cimport obi_open_dms, \ obi_close_dms, \ obi_dms_exists, \ obi_dms_get_full_path, \ - obi_close_atexit + obi_close_atexit, \ + obi_dms_write_comments from .capi.obitypes cimport const_char_p from obitools3.utils cimport bytes2str, \ str2bytes, \ tobytes, \ - tostr + tostr, \ + bytes2str_object, \ + str2bytes_object, \ + clean_empty_values_from_object from .object cimport OBIDeactivatedInstanceError @@ -25,6 +29,9 @@ from pathlib import Path from .view import view from .object import OBIWrapper +import json +import time + cdef class DMS(OBIWrapper): @@ -107,6 +114,21 @@ cdef class DMS(OBIWrapper): return self.pointer().dms_name + # command history DOT graph property getter in the form of a bytes string + @property + def dot_history_graph(self): + complete_graph = [] + for view_name in self: + complete_graph.extend(self[view_name].dot_history_graph_list) + uniq_graph = [] + for elt in complete_graph: + if elt not in uniq_graph: + uniq_graph.append(elt) + uniq_graph.insert(0, b"digraph \""+self.name+b"\" {\n") + uniq_graph.append(b"}") + return b"".join(uniq_graph) + + @OBIWrapper.checkIsActive def keys(self) : @@ -181,6 +203,129 @@ cdef class DMS(OBIWrapper): for view_name in self.keys(): s = s + repr(self.get_view(view_name)) + "\n" return s + + + @OBIWrapper.checkIsActive + def record_command_line(self, command_line): + t = time.asctime(time.localtime(time.time())) + if "command_line_history" not in self.comments: + l = [] + else: + l = self.comments["command_line_history"] + l.append({"command":command_line, "time":t}) + self.comments["command_line_history"] = l + + + # comments property getter + @property + def comments(self): + return DMS_comments(self) + @comments.setter + def comments(self, object value): + DMS_comments(self, value) + # bash command history property getter + @property + def bash_history(self): + s = b"#!/bin/bash\n\n" + first = True + for command in self.command_line_history: + s+=b"#" + s+=command[b"time"] + s+=b"\n" + s+=command[b"command"] + s+=b"\n" + return s + + + # command line history property getter + @property + def command_line_history(self): + return self.comments[b"command_line_history"] + + +cdef class DMS_comments(dict): # Not thread safe + def __init__(self, DMS dms, value=None) : + if not dms.active() : + raise OBIDeactivatedInstanceError() + self._dms = dms + if value is not None: + self.update(value) # TODO test and discuss not overwriting (could use replace bool) + self._update_from_file() + + def _update_from_file(self): + cdef bytes comments_json + cdef str comments_json_str + cdef OBIDMS_p dms_p + cdef DMS dms + if not self._dms.active() : + raise OBIDeactivatedInstanceError() + dms = self._dms + dms_p = (dms.pointer()) + comments_json = dms_p.infos.comments + comments_json_str = bytes2str(comments_json) + comments_dict = json.loads(comments_json_str) + str2bytes_object(comments_dict) + super(DMS_comments, self).update(comments_dict) + + def __getitem__(self, object key): + if not self._dms.active() : + raise OBIDeactivatedInstanceError() + if type(key) == str: + key = str2bytes(key) + self._update_from_file() + return super(DMS_comments, self).__getitem__(key) + + def __setitem__(self, object key, object value): + cdef OBIDMS_p dms_p + cdef DMS dms + + if not self._dms.active() : + raise OBIDeactivatedInstanceError() + + dms = self._dms + dms_p = (dms.pointer()) + + # Remove virtually empty values from the object # TODO discuss + clean_empty_values_from_object(value) + + # If value is virtually empty, don't add it # TODO discuss + if value is None or len(value) == 0: + return + + # Convert to bytes + if type(key) == str: + key = str2bytes(key) + value_bytes = str2bytes_object(value) + + # Update dict with comments already written in file + self._update_from_file() + + # Add new element # TODO don't overwrite? + super(DMS_comments, self).__setitem__(key, value_bytes) + + # Convert to str because json library doens't like bytes + dict_str = {key:item for key,item in self.items()} + dict_str = bytes2str_object(dict_str) + + # Convert to json string + comments_json = json.dumps(dict_str) + + # Write new comments + if obi_dms_write_comments(dms_p, tobytes(comments_json)) < 0: + raise Exception("Could not write DMS comments, DMS:", dms.name, "comments:", comments_json) + + def update(self, value): + for k,v in value.items(): + self[k] = v + + def __contains__(self, key): + return super(DMS_comments, self).__contains__(tobytes(key)) + + def __str__(self): + return bytes2str(self._dms.pointer().infos.comments) + + + \ No newline at end of file