-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtoThingist.py
executable file
·168 lines (133 loc) · 6.12 KB
/
toThingist.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/python
# This is NOT a typo - using the system python is deliberate.
import ConfigParser
import optparse
import json
import sys
import os.path
import tempfile
import todoistinterface
import thingsinterface
"""toThingist - sync between Todoist and Cultured Code's Things"""
BASE_STATE = {"incomplete": [], "complete": [],
"todoist_to_things": {}, "things_to_todoist": {}}
class ToThingist(object):
def __init__(self, todoist_obj, things_location, state):
self.todoist = todoist_obj
self.things_location = things_location
self.state = state
def sync_things_to_todoist(self, verbose=False):
"""
Sync the Things location to the ToDoist inbox.
Args:
verbose: bool. If true, debug output will go to stderr.
"""
inbox_id = self.todoist.get_inbox_id()
for todo in thingsinterface.ToDos(self.things_location):
if todo.thingsid in self.state["things_to_todoist"]:
todoist_id = self.state["things_to_todoist"][todo.thingsid]
if todo.is_closed() or todo.is_cancelled():
complete_result = self.todoist.set_complete(todoist_id)
if verbose:
sys.stderr.write(
"Marking task '%s' as complete in ToDoist\n" % todo.name
)
if verbose:
sys.stderr.write(
"Todo %s (\"%s\") synced already\n" % (
todo.thingsid, todo.name
)
)
continue
z = self.todoist.create_todo(todo.name, inbox_id)
todoist_id = z["id"]
self.state["todoist_to_things"][todoist_id] = todo.thingsid
self.state["things_to_todoist"][todo.thingsid] = todoist_id
#TODO better return
return self.state
def sync_todoist_to_things(self, tag_import=False,
verbose=False):
"""Sync todoist inbox todos into a given Things location.
Args:
tag_import: tag all imported todos with "todoist_sync"
verbose: bool, if True output debug to stderr
"""
for project in self.todoist.get_projects():
if project["name"] == "Inbox":
todoist_todos = self.todoist.get_all_todos(project["id"])
for todoist_todo in todoist_todos:
creation_date = todoist_todo["date_added"]
name = todoist_todo["content"]
todoist_id = str(todoist_todo["id"])
if todoist_id in self.state["todoist_to_things"]:
if verbose:
sys.stderr.write(
"Todo %s (\"%s\") synced already\n" % (
todoist_id, name)
)
if todoist_todo["checked"] == 1:
# todo is checked off - check off locally
to_complete = thingsinterface.ToDo._getTodoByID(
self.state["todoist_to_things"][todoist_id])
to_complete.complete()
if verbose:
sys.stderr.write(
"marked '%s' as complete" % name
)
continue
tags = []
if tag_import:
tags = ["todoist_sync"]
if todoist_todo["checked"] != 1:
newtodo = thingsinterface.ToDo(name=name,
tags=tags,
location=self.things_location)
self.state[
"todoist_to_things"][todoist_id] = newtodo.thingsid
self.state[
"things_to_todoist"][newtodo.thingsid] = todoist_id
# TODO better return
return self.state
def main():
parser = optparse.OptionParser()
parser.add_option("-v", "--verbose", dest="verbose",
help="Be verbose",
action="store_true")
parser.add_option("-c", "--config",
action="store", dest="configpath",
default="~/.tothingist",
help="alternate path for configuration file")
(options, args) = parser.parse_args()
config = ConfigParser.ConfigParser()
config.read(os.path.expanduser(options.configpath))
api_key = config.get('login', 'api_token')
statefile = os.path.expanduser(config.get("config", "statefile"))
things_location = config.get("config", "thingslocation")
todoist_obj = todoistinterface.ToDoistInterface(api_key)
state = BASE_STATE.copy()
if statefile and os.path.isfile(statefile):
state_f = open(statefile)
try:
state = json.loads(state_f.read())
except ValueError:
print "Failed to open state file! Is it valid JSON?"
raise SystemExit(1)
state_f.close()
tothingist_obj = ToThingist(todoist_obj, things_location, state)
tothingist_obj.sync_todoist_to_things(tag_import=True,
verbose=options.verbose)
tothingist_obj.sync_things_to_todoist(verbose=options.verbose)
if statefile:
if not tothingist_obj.state:
sys.stderr.write(("Not writing state file as there is no content"
" to sync. This could be in error or you'll need"
" to create at least one todo. "))
else:
# Avoid zeroing the file when the user's system has no
# disk space by writing a tempfile
temp_state_filename = tempfile.mkstemp(text=True)
with open(temp_state_filename[1], "w") as temp_state_f:
temp_state_f.write(json.dumps(tothingist_obj.state))
os.rename(temp_state_filename[1], statefile)
if __name__ == "__main__":
main()