Passed
Push — master ( 914db1...556204 )
by Alexander
04:15 queued 16s
created

things3.things3_api.Things3API.api()   A

Complexity

Conditions 2

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 12
nop 2
dl 0
loc 14
rs 9.8
c 0
b 0
f 0
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
"""Simple read-only Things 3 Web Serivce."""
5
6
from __future__ import print_function
7
8
__author__ = "Alexander Willner"
9
__copyright__ = "Copyright 2020 Alexander Willner"
10
__credits__ = ["Alexander Willner"]
11
__license__ = "Apache License 2.0"
12
__version__ = "2.5.3"
13
__maintainer__ = "Alexander Willner"
14
__email__ = "[email protected]"
15
__status__ = "Development"
16
17
import sys
18
from os import getcwd
19
import json
20
import socket
21
from flask import Flask
22
from flask import Response
23
from flask import request
24
from werkzeug.serving import make_server
25
from things3.things3 import Things3
26
27
28
class Things3API():
29
    """API Wrapper for the simple read-only API for Things 3."""
30
31
    PATH = getcwd() + '/resources/'
32
    DEFAULT = 'kanban.html'
33
    test_mode = "task"
34
    host = 'localhost'
35
    port = 15000
36
37
    def on_get(self, url=DEFAULT):
38
        """Handles other GET requests"""
39
        status = 200
40
        filename = self.PATH + url
41
        content_type = 'application/json'
42
        if filename.endswith('css'):
43
            content_type = 'text/css'
44
        if filename.endswith('html'):
45
            content_type = 'text/html'
46
        if filename.endswith('js'):
47
            content_type = 'text/javascript'
48
        if filename.endswith('png'):
49
            content_type = 'image/png'
50
        if filename.endswith('jpg'):
51
            content_type = 'image/jpeg'
52
        if filename.endswith('ico'):
53
            content_type = 'image/x-ico'
54
        try:
55
            with open(filename, 'rb') as source:
56
                data = source.read()
57
        except FileNotFoundError:
58
            data = 'not found'
59
            content_type = 'text'
60
            status = 404
61
        return Response(response=data,
62
                        content_type=content_type,
63
                        status=status)
64
65
    def mode_selector(self):
66
        """Switch between project and task mode"""
67
        try:
68
            mode = request.args.get('mode')
69
        except RuntimeError:
70
            mode = 'task'
71
        if mode == "project" or self.test_mode == "project":
72
            self.things3.mode_project()
73
74
    def config_get(self, key):
75
        """Read key from config"""
76
        data = self.things3.get_config(key)
77
        return Response(response=data)
78
79
    def config_set(self, key):
80
        """Write key to config"""
81
        value = request.get_data().decode('utf-8')
82
        self.things3.set_config(key, value)
83
        return Response()
84
85
    def tag(self, tag, area=None):
86
        """Get specific tag."""
87
        self.mode_selector()
88
        if area is not None:
89
            data = self.things3.get_tag_today(tag)
90
        else:
91
            data = self.things3.get_tag(tag)
92
        self.things3.mode_task()
93
        data = json.dumps(data)
94
        return Response(response=data, content_type='application/json')
95
96
    def api(self, command):
97
        """Return database as JSON strings."""
98
        if command in self.things3.functions:
99
            func = self.things3.functions[command]
100
            self.mode_selector()
101
            data = func(self.things3)
102
            self.things3.mode_task()
103
            data = json.dumps(data)
104
            return Response(response=data, content_type='application/json')
105
106
        data = json.dumps(self.things3.get_not_implemented())
107
        return Response(response=data,
108
                        content_type='application/json',
109
                        status=404)
110
111
    def get_url(self):
112
        """Get the public url for the endpoint"""
113
        return f"http://{socket.gethostname()}:{self.port}"
114
115
    def api_filter(self, mode, uuid):
116
        """Filter view by specific modifiers"""
117
        if mode == "area" and uuid != "":
118
            self.things3.filter = f"TASK.area = '{uuid}' AND"
119
        if mode == "project" and uuid != "":
120
            self.things3.filter = f"""
121
                (TASK.project = '{uuid}' OR HEADING.project = '{uuid}') AND
122
                """
123
        return Response(status=200)
124
125
    def api_filter_reset(self):
126
        """Reset filter modifiers"""
127
        self.things3.filter = ""
128
        return Response(status=200)
129
130
    def __init__(self, database=None, host=None, port=None, expose=None):
131
        self.things3 = Things3(database=database)
132
133
        cfg = self.things3.get_from_config(host, 'KANBANVIEW_HOST')
134
        self.host = cfg if cfg else self.host
135
        self.things3.set_config('KANBANVIEW_HOST', self.host)
136
137
        cfg = self.things3.get_from_config(port, 'KANBANVIEW_PORT')
138
        self.port = cfg if cfg else self.port
139
        self.things3.set_config('KANBANVIEW_PORT', self.port)
140
141
        cfg = self.things3.get_from_config(expose, 'API_EXPOSE')
142
        self.host = '0.0.0.0' if (str(cfg).lower() == 'true') else 'localhost'
143
        self.things3.set_config('KANBANVIEW_HOST', self.host)
144
        self.things3.set_config('API_EXPOSE', str(cfg).lower() == 'true')
145
146
        self.flask = Flask(__name__)
147
        self.flask.add_url_rule('/config/<key>', view_func=self.config_get)
148
        self.flask.add_url_rule(
149
            '/config/<key>', view_func=self.config_set, methods=["PUT"])
150
        self.flask.add_url_rule('/api/<command>', view_func=self.api)
151
        self.flask.add_url_rule('/api/url', view_func=self.get_url)
152
        self.flask.add_url_rule('/api/tag/<tag>', view_func=self.tag)
153
        self.flask.add_url_rule('/api/tag/<tag>/<area>', view_func=self.tag)
154
        self.flask.add_url_rule(
155
            '/api/filter/<mode>/<uuid>', view_func=self.api_filter)
156
        self.flask.add_url_rule('/api/filter/reset',
157
                                view_func=self.api_filter_reset)
158
        self.flask.add_url_rule('/<url>', view_func=self.on_get)
159
        self.flask.add_url_rule('/', view_func=self.on_get)
160
        self.flask.app_context().push()
161
        self.flask_context = None
162
163
    def main(self):
164
        """"Main function."""
165
        print(f"Serving at http://{self.host}:{self.port} ...")
166
167
        try:
168
            self.flask_context = make_server(
169
                self.host, self.port, self.flask, threaded=True)
170
            self.flask_context.serve_forever()
171
        except KeyboardInterrupt:
172
            print("Shutting down...")
173
            sys.exit(0)
174
175
176
def main():
177
    """Main entry point for CLI installation"""
178
    Things3API().main()
179
180
181
if __name__ == "__main__":
182
    main()
183