Skip to content

Commit

Permalink
Add & implement org.gnome.GTG.Tasks DBus interface
Browse files Browse the repository at this point in the history
Introducing org.gnome.GTG.Tasks, an DBus interface to manage tasks. This
is very much inspired by the old "DBus Wrapper", that implemented
org.gnome.GTGTasks (I think), but this is in some ways different:

* Do actions in batches
* Only handle tasks, no UI stuff (actions can replace some)
  • Loading branch information
Neui committed Oct 9, 2020
1 parent 56390a7 commit 115a86b
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 0 deletions.
162 changes: 162 additions & 0 deletions GTG/core/dbus/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@

# Implementation of the org.gnome.GTG.Tasks interface

from gi.repository import GLib
from GTG.core.logger import log
from GTG.core.search import parse_search_query, InvalidQuery
from GTG.core.dates import Date
from .dbus import DBusInterfaceImplService, DBusReturnError


def _task_to_dict(task):
d = {
"id": task.get_id(),
"status": task.get_status(),
"title": task.get_title(),
"duedate": str(task.get_due_date()), # TODO: Check if str(Date) is correct
"startdate": str(task.get_start_date()), # TODO: Check if str(Date) is correct
"donedate": str(task.get_closed_date()), # TODO: Check if str(Date) is correct
"tags": task.get_tags_name(),
"text": task.get_text(),
"subtasks": task.get_children(),
"parents": task.get_parents(),
}
return d

_task_dict_to_variant_type = {
"id": 's',
"status": 's',
"title": 's',
"duedate": 's',
"startdate": 's',
"donedate": 's',
"tags": 'as',
"text": 's',
"subtasks": 'as',
"parents": 'as',
}

def _task_dict_to_variant(task_dict):
"""Convert an task dict to a variant dict to be submitted over DBus"""
d = dict(task_dict)
for name, vtype in _task_dict_to_variant_type.items():
if name in task_dict:
d[name] = GLib.Variant(vtype, task_dict[name])
return d # Not GLib.Variant("a{sv}", d) because it'll break later
# return GLib.Variant("a{sv}", d)

def _task_to_variant(task):
"""Convert an task object to a variant dict to be submitted over DBus"""
return _task_dict_to_variant(_task_to_dict(task))

def _variant_to_task_dict(task_variant):
"""Convert an variant dict to a task"""
return task_variant.unpack()


class DBusImplTasks(DBusInterfaceImplService):
INTERFACE_NAME = 'org.gnome.GTG.Tasks'

def __init__(self, req):
super().__init__()
self.req = req

tree = req.get_main_view()
# TODO: Register signals
# tree.register_cllbck('node-added', lambda tid, _:
# self.TaskAdded(tid))
# tree.register_cllbck('node-modified', lambda tid, _:
# self.TaskModified(tid))
# tree.register_cllbck('node-deleted', lambda tid, _:
# self.TaskDeleted(tid))

def GetTasks(self, tids):
log.debug(f"Doing GetTasks({tids})")
# TODO: Improve on better error message on missing tasks or something
# Current: Fehler: GDBus.Error:python.AttributeError: 'NoneType' object has no attribute 'get_id'
return [_task_to_variant(self.req.get_task(tid)) for tid in tids]

def GetActiveTasks(self):
log.debug(f"Doing GetActiveTasks()")
return self.GetTasksFiltered(['active', 'workable'])

def GetTaskIdsFiltered(self, filters):
log.debug(f"Doing GetTasksFiltered({filters})")
tree = self.req.get_tasks_tree().get_basetree()
view = tree.get_viewtree()
for filter in filters:
if filter[0] == '!':
view.apply_filter(filter[1:], parameters={'negate': 1})
else:
view.apply_filter(filter)
return view.get_all_nodes() # TODO: Check what it returns (type-wise)

def GetTasksFiltered(self, filters):
log.debug(f"Doing GetTasksFiltered({filters})")
return self.GetTasks(self.GetTaskIdsFiltered(filters))

def SearchTaskIds(self, query):
log.debug(f"Doing SearchTaskIds({query})")
tree = self.req.get_tasks_tree().get_basetree()
view = tree.get_viewtree()
try:
search = parse_search_query(query)
view.apply_filter('search', parameters=search)
tasks = view.get_all_nodes()
if tasks:
return tasks
except InvalidQuery:
raise DBusReturnError("gtg.InvalidQuery", "Invalid Query: " + str(query))
return []

def SearchTasks(self, query):
log.debug(f"Doing SearchTasks({query})")
return self.GetTasks(self.SearchTaskIds(query))

def HasTasks(self, tids):
log.debug(f"Doing HasTasks({tids})")
return {tid: self.req.has_task(tid) for tid in tids}

def DeleteTasks(self, tids):
log.debug(f"Doing DeleteTasks({tids})")
d = {}
for tid in tids:
if self.req.has_task(tid):
self.req.delete_task(tid)
d[tid] = True
else:
d[tid] = False
return d

def NewTasks(self, tasks):
log.debug(f"Doing NewTasks({tasks})")
return [] # TODO: Implement this

def ModifyTasks(self, patches):
log.debug(f"Doing ModifyTasks({patches})")
r = []
for patch in patches:
r.append(_task_to_variant(self._modify_task(patch)))
return r

def _modify_task(self, patch):
"""Modify a single task and return it"""
task = self.req.get_task(patch["id"])
if "title" in patch:
task.set_title(patch["task"])
if "text" in patch:
task.set_text(patch["text"])
if "duedate" in patch:
task.set_due_date(Date.parse(patch["due_date"]))
if "startdate" in patch:
task.set_start_date(Date.parse(patch["start_date"]))
if "donedate" in patch:
pass # TODO
if "tags" in patch:
pass # TODO
if "childrens" in patch:
pass # TODO
if "parents" in patch:
pass # TODO
return task

1 change: 1 addition & 0 deletions GTG/core/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ gtg_core_plugin_sources = [
gtg_core_dbus_sources = [
'dbus/__init__.py',
'dbus/dbus.py',
'dbus/tasks.py',
]

python3.install_sources(gtg_core_sources, subdir: 'GTG' / 'core', pure: true)
Expand Down
8 changes: 8 additions & 0 deletions GTG/gtk/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from GTG.gtk.backends import BackendsDialog
from GTG.gtk.browser.tag_editor import TagEditor
from GTG.core.timer import Timer
from GTG.core.dbus.tasks import DBusImplTasks


class Application(Gtk.Application):
Expand Down Expand Up @@ -115,6 +116,10 @@ def do_startup(self):

self.init_style()

self.dbus_tasks = DBusImplTasks(self.req)
self.dbus_tasks.dbus_register(self.get_dbus_connection(),
self.get_dbus_object_path())

def do_activate(self):
"""Callback when launched from the desktop."""

Expand Down Expand Up @@ -552,6 +557,9 @@ def do_shutdown(self):
# Save data and shutdown datastore backends
self.req.save_datastore(quit=True)

if self.dbus_tasks:
self.dbus_tasks.dbus_unregister()

Gtk.Application.do_shutdown(self)

# --------------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions data/dbus/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
configure_file(
input: rdnn_name + '.Tasks.xml',
output: rdnn_name + '.Tasks.xml',
copy: true,
install: true,
install_dir: dbusinterfacesdir
)

179 changes: 179 additions & 0 deletions data/dbus/org.gnome.GTG.Tasks.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/org/gnome/GTG">
<!--
org.gnome.GTG.Tasks:
@short_description: Manage GTG Tasks
Interface that GTG implements to let other applications view and edit
tasks.
An common type used here would be the so-called "task dict(ionary)" or
{sv}. It means it is a map of string to generic values. They could
contain:
* id (s): The id the task can be identified
* title (s): Title of the task
* status (i): 0: Unknown, 1: Open, 2: Closed
* text (s): Text content of the task
* duedate (s): Due date in ISO TODO, or a fuzzy date "now", "soon", "someday"
* startdate (s): Start date in ISO TODO, or a fuzzy date "now", "soon", "someday"
* donedate (s): When the task was finished, in ISO TODO
* tags (as): Tasks the task has, without @
* subtasks (as): Subtasks as UIDs the task contains
* parent (s): ID of the parent task (empty or non-existent means root)
-->
<interface name="org.gnome.GTG.Tasks">
<!--
GetTasks:
@uids: An array of Task UIDs, whose info should be retrieved
@tasks: An dictionary of UIDs to their task dictionary.
TODO: API documentation
-->
<method name="GetTasks">
<arg name="uids" type="as" direction="in"/>
<arg name="tasks" type="aa{sv}" direction="out"/>
<!-- <arg name="tasks" type="a{sa{sv}}" direction="out"/> -->
</method>

<!--
GetTasks:
@tasks: An array of tasks dictionary that are currently active.
TODO: API documentation, explain what active means
Refer to the interface description for the task dict values.
-->
<method name="GetActiveTasks">
<arg name="tasks" type="aa{sv}" direction="out"/>
</method>

<!--
GetTaskIdsFiltered:
@filter: Filters to apply, also see filters_bank documentation
@task_ids: Filtered tasks ids matching the specified filters
TODO: API documentation
Also see the GetTaskFiltered method to find more about filters.
-->
<method name="GetTaskIdsFiltered">
<arg name="filter" type="as" direction="in"/>
<arg name="tasks_ids" type="as" direction="out"/>
</method>

<!--
GetTasksFiltered:
@filter: Filters to apply, also see filters_bank documentation
@tasks: Filtered tasks dicts matching the specified filters
TODO: API documentation, possibly explain some of filters_bank
-->
<method name="GetTasksFiltered">
<arg name="filter" type="as" direction="in"/>
<arg name="tasks" type="aa{sv}" direction="out"/>
</method>

<!--
SearchTasks:
@query: Search for tasks, like search bar with special options
@tasks: Filtered tasks ids matching the specified filters
TODO: API documentation, possibly explain some of the specials
(If used, need to check)
-->
<method name="SearchTaskIds">
<arg name="query" type="s" direction="in"/>
<arg name="tasks" type="as" direction="out"/>
</method>

<!--
SearchTasks:
@query: Search for tasks, like search bar with special options
@tasks: Filtered tasks dicts matching the specified filters
TODO: API documentation, possibly explain some of the specials
(If used, need to check)
-->
<method name="SearchTasks">
<arg name="query" type="s" direction="in"/>
<arg name="tasks" type="aa{sv}" direction="out"/>
</method>

<!--
HasTasks:
@tids: Array of task IDs to check
@successful: Map of task IDs to a boolean indicating it exists
TODO: API documentation
-->
<method name="HasTasks">
<arg name="tids" type="as" direction="in"/>
<arg name="present" type="a{sb}" direction="out"/>
</method>

<!--
DeleteTasks:
@task_ids: Array of task IDs to delete
@successful: Map of task IDs to a boolean indication success deleting
TODO: API documentation
-->
<method name="DeleteTasks">
<arg name="task_ids" type="as" direction="in"/>
<arg name="successful" type="a{sb}" direction="out"/>
</method>

<!--
NewTasks:
@data: Array of task dictionaries to add
@ids: Array of the new task IDs for the tasks
TODO: API documentation
-->
<method name="NewTasks">
<arg name="data" type="aa{sv}" direction="in"/>
<arg name="ids" type="as" direction="out"/>
</method>

<!--
ModifyTasks:
@patch: Array of task dictionaries to update, uses the tid
@resulting: TODO
TODO: API documentation
-->
<method name="ModifyTasks">
<arg name="patch" type="aa{sv}" direction="in"/>
<arg name="result" type="aa{sv}" direction="out"/>
</method>

<!--
TaskAdded:
@tid: Task ID that has been added
TODO: API documentation
-->
<signal name="TaskAdded">
<arg name="tid" type="s"/>
</signal>

<!--
TaskAdded:
@tid: Task ID that has been modified
TODO: API documentation
-->
<signal name="TaskModified">
<arg name="tid" type="s"/>
</signal>

<!--
TaskAdded:
@tid: Task ID that has been deleted
TODO: API documentation
-->
<signal name="TaskDeleted">
<arg name="tid" type="s"/>
</signal>
</interface>
</node>
1 change: 1 addition & 0 deletions data/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ configure_file(
)

subdir('icons')
subdir('dbus')
Loading

0 comments on commit 115a86b

Please sign in to comment.