Passed
Push — master ( d52a5c...eb2b83 )
by Alexander
01:44
created

src.things3.Things3.get_not_implemented()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
"""Simple read-only API for Things 3."""
5
6
from __future__ import print_function
7
8
__author__ = "Alexander Willner"
9
__copyright__ = "2020 Alexander Willner"
10
__credits__ = ["Alexander Willner"]
11
__license__ = "Apache"
12
__version__ = "2.0.0"
13
__maintainer__ = "Alexander Willner"
14
__email__ = "[email protected]"
15
__status__ = "Development"
16
17
import sqlite3
18
from os.path import expanduser
19
from os import environ
20
21
22
class Things3():
23
    """Simple read-only API for Things 3."""
24
    # Variables
25
    database = None
26
    cursor = None
27
    json = False
28
    tag_waiting = "Waiting" if not environ.get('TAG_WAITING') \
29
        else environ.get('TAG_WAITING')
30
    tag_mit = "MIT" if not environ.get('TAG_MIT') \
31
        else environ.get('TAG_MIT')
32
33
    # Database info
34
    FILE_SQLITE = '~/Library/Containers/'\
35
                  'com.culturedcode.ThingsMac.beta/Data/Library/'\
36
                  'Application Support/Cultured Code/Things/Things.sqlite3'\
37
        if not environ.get('THINGSDB') else environ.get('THINGSDB')
38
    TASKTABLE = "TMTask"
39
    AREATABLE = "TMArea"
40
    TAGTABLE = "TMTag"
41
    TASKTAGTABLE = "TMTaskTag"
42
    ISNOTTRASHED = "TASK.trashed = 0"
43
    ISTRASHED = "TASK.trashed = 1"
44
    ISOPEN = "TASK.status = 0"
45
    ISNOTSTARTED = "TASK.start = 0"
46
    ISCANCELLED = "TASK.status = 2"
47
    ISCOMPLETED = "TASK.status = 3"
48
    ISSTARTED = "TASK.start = 1"
49
    ISPOSTPONED = "TASK.start = 2"
50
    ISTASK = "TASK.type = 0"
51
    ISPROJECT = "TASK.type = 1"
52
    ISHEADING = "TASK.type = 2"
53
    ISOPENTASK = ISTASK + " AND " + ISNOTTRASHED + " AND " + ISOPEN
54
55
    # Query Index
56
    I_UUID = 0
57
    I_TITLE = 1
58
    I_CONTEXT = 2
59
    I_CONTEXT_UUID = 3
60
    I_DUE = 4
61
62
    def __init__(self,
63
                 database=FILE_SQLITE,
64
                 tag_waiting='Waiting',
65
                 tag_mit='MIT',
66
                 json=False):
67
        self.database = expanduser(database)
68
        self.tag_mit = tag_mit
69
        self.tag_waiting = tag_waiting
70
        self.cursor = sqlite3.connect(self.database).cursor()
71
        self.json = json
72
73
    def get_inbox(self):
74
        """Get all tasks from the inbox."""
75
        query = self.ISOPENTASK + " AND " + self.ISNOTSTARTED + \
76
            " ORDER BY TASK.duedate DESC , TASK.todayIndex"
77
        return self.get_rows(query)
78
79
    def get_today(self):
80
        """Get all tasks from the today list."""
81
        query = self.ISOPENTASK + " AND " + self.ISSTARTED + \
82
            " AND TASK.startdate is NOT NULL" + \
83
            " ORDER BY TASK.duedate DESC , TASK.todayIndex"
84
        return self.get_rows(query)
85
86
    def get_someday(self):
87
        """Get someday tasks."""
88
        query = self.ISOPENTASK + " AND " + self.ISPOSTPONED + \
89
            " AND TASK.startdate IS NULL AND TASK.recurrenceRule IS NULL" + \
90
            " ORDER BY TASK.duedate DESC, TASK.creationdate DESC"
91
        return self.get_rows(query)
92
93
    def get_upcoming(self):
94
        """Get upcoming tasks."""
95
        query = self.ISOPENTASK + " AND " + self.ISPOSTPONED + \
96
            " AND (TASK.startDate NOT NULL " + \
97
            "      OR TASK.recurrenceRule NOT NULL)" + \
98
            " ORDER BY TASK.startdate, TASK.todayIndex"
99
        return self.get_rows(query)
100
101
    def get_waiting(self):
102
        """Get waiting tasks."""
103
        query = self.ISOPENTASK + \
104
            " AND TAGS.tags=(SELECT uuid FROM " + self.TAGTABLE + \
105
            " WHERE title='" + self.tag_waiting + "')" + \
106
            " ORDER BY TASK.duedate DESC , TASK.todayIndex"
107
        return self.get_rows(query)
108
109
    def get_mit(self):
110
        """Get most important tasks."""
111
        query = self.ISOPENTASK + " AND " + self.ISSTARTED + \
112
            " AND PROJECT.status = 0 " \
113
            " AND TAGS.tags=(SELECT uuid FROM " + self.TAGTABLE + \
114
            " WHERE title='" + self.tag_mit + "')" + \
115
            " ORDER BY TASK.duedate DESC , TASK.todayIndex"
116
        return self.get_rows(query)
117
118
    def get_anytime(self):
119
        """Get anytime tasks."""
120
        query = self.ISOPENTASK + " AND " + self.ISSTARTED + \
121
            " AND TASK.startdate is NULL" + \
122
            " AND (TASK.area NOT NULL OR TASK.project in " + \
123
            "(SELECT uuid FROM " + self.TASKTABLE + \
124
            " WHERE uuid=TASK.project AND start=1" + \
125
            " AND trashed=0))" + \
126
            " ORDER BY TASK.duedate DESC , TASK.todayIndex"
127
        return self.get_rows(query)
128
129
    @staticmethod
130
    def get_not_implemented():
131
        """Not implemented warning."""
132
        return [["0", "not implemented", "no context", "0", "0"]]
133
134
    def get_rows(self, sql):
135
        """Query Things database."""
136
137
        sql = """
138
            SELECT DISTINCT
139
                TASK.uuid,
140
                TASK.title,
141
                CASE
142
                    WHEN AREA.title IS NOT NULL THEN AREA.title
143
                    WHEN PROJECT.title IS NOT NULL THEN PROJECT.title
144
                    WHEN HEADING.title IS NOT NULL THEN HEADING.title
145
                END,
146
                CASE
147
                    WHEN AREA.uuid IS NOT NULL THEN AREA.uuid
148
                    WHEN PROJECT.uuid IS NOT NULL THEN PROJECT.uuid
149
                END,
150
                CASE
151
                    WHEN TASK.recurrenceRule IS NULL
152
                    THEN date(TASK.dueDate,"unixepoch")
153
                ELSE NULL
154
                END
155
            FROM
156
                TMTask AS TASK
157
            LEFT JOIN
158
                TMTaskTag TAGS ON TAGS.tasks = TASK.uuid
159
            LEFT OUTER JOIN
160
                TMTask PROJECT ON TASK.project = PROJECT.uuid
161
            LEFT OUTER JOIN
162
                TMArea AREA ON TASK.area = AREA.uuid
163
            LEFT OUTER JOIN
164
                TMTask HEADING ON TASK.actionGroup = HEADING.uuid
165
            WHERE """ + sql
166
        self.cursor.execute(sql)
167
        return self.cursor.fetchall()
168
169
    def convert_task_to_model(self, task):
170
        """Convert task to model."""
171
        model = {'uuid': task[self.I_UUID],
172
                 'title': task[self.I_TITLE],
173
                 'context': task[self.I_CONTEXT],
174
                 'context_uuid': task[self.I_CONTEXT_UUID],
175
                 'due': task[self.I_DUE],
176
                 }
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (remove 1 space).
Loading history...
177
        return model
178
179
    def convert_tasks_to_model(self, tasks):
180
        """Convert tasks to model."""
181
        model = []
182
        for task in tasks:
183
            model.append(self.convert_task_to_model(task))
184
        return model
185