-
Notifications
You must be signed in to change notification settings - Fork 0
/
dropbox-event-feed-to-mail.py
executable file
·168 lines (145 loc) · 7.2 KB
/
dropbox-event-feed-to-mail.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/python3
# require python3
# -*- coding: utf-8 -*-
# To generate a token look at: https://www.dropbox.com/developers/documentation/http/documentation
DROPBOX_APP_TOKEN = '...'
# Email changes to this address
EMAIL_TO = '...'
# Save temporary data (cursor) to this file
CURSOR_FILE = 'dropbox-event-feed-to-mail.data'
# Commands used
COMMAND_GET_LATEST_CURSOR = 'curl -s -X POST https://api.dropboxapi.com/2/files/list_folder/get_latest_cursor --header "Authorization: Bearer {TOKEN}" --header "Content-Type: application/json" --data "{\\"path\\": \\"\\",\\"recursive\\": true,\\"include_deleted\\": true}"'
COMMAND_LIST_FOLDER_CONTINUE = 'curl -s -X POST https://api.dropboxapi.com/2/files/list_folder/continue --header "Authorization: Bearer {TOKEN}" --header "Content-Type: application/json" --data "{\\"cursor\\": \\"{CURSOR}\\"}"'
#COMMAND_SEND_MAIL = 'echo -ne "Subject: {SUBJECY}\nContent-Type: text/html\n\n{BODY}" | sendmail {EMAIL_TO}'
COMMAND_SEND_MAIL = 'sendmail {EMAIL_TO}'
# Dropbox has a strange behaviour. Often it gives 0 results, but with the "has_more" property set to TRUE. Retrying the fetch will give correct results. But sometimes we can keep retrying and get nothing. So a "max number of retries" is needed
LOOP_MAX = 100
# Max records in a single mail (if more records are presents, the script will split them in multiple mails)
RECORDS_MAX = 20000
#------------------------------------------------------------------------------
import logging
import subprocess
import os
import json
import quopri
import time
currdir = os.path.realpath(os.path.dirname(__file__))
def send_mail(subject, body):
try:
input = "Subject: " + subject + "\nContent-Type: text/html; charset=\"UTF-8\"\nContent-Transfer-Encoding: quoted-printable\n\n" + body
output = subprocess.check_output(COMMAND_SEND_MAIL.replace('{EMAIL_TO}', EMAIL_TO), input = quopri.encodestring(input.encode("utf-8")), shell=True).decode("utf-8")
#output = subprocess.check_output(COMMAND_SEND_MAIL.replace('{SUBJECY}', subject.replace('"', '\\"')).replace('{BODY}', body.replace('"', '\\"')).replace('{EMAIL_TO}', EMAIL_TO), shell=True).decode("utf-8")
return True
except:
logging.exception("failed sending mail")
return False
def cursor_save(cursor):
try:
if os.path.isfile(currdir + '/' + CURSOR_FILE + '.new'):
os.remove(currdir + '/' + CURSOR_FILE + '.new')
with open(currdir + '/' + CURSOR_FILE + '.new', 'w') as f:
f.write(cursor)
if os.path.isfile(currdir + '/' + CURSOR_FILE + '.new'):
if os.path.isfile(currdir + '/' + CURSOR_FILE):
if os.path.isfile(currdir + '/' + CURSOR_FILE + '.bak'):
os.remove(currdir + '/' + CURSOR_FILE + '.bak')
os.rename(currdir + '/' + CURSOR_FILE, currdir + '/' + CURSOR_FILE + '.bak')
os.rename(currdir + '/' + CURSOR_FILE + '.new', currdir + '/' + CURSOR_FILE)
except:
logging.exception("failed saving cursor file")
# @see https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-get_latest_cursor
def cursor_fetch():
cursor = None
if os.path.isfile(currdir + '/' + CURSOR_FILE):
with open(currdir + '/' + CURSOR_FILE, 'r') as f:
cursor = f.read().strip()
else:
try:
cmd = COMMAND_GET_LATEST_CURSOR.replace('{TOKEN}', DROPBOX_APP_TOKEN)
print("Fetching " + cmd + ' ...')
output = subprocess.check_output(cmd, shell=True).decode("utf-8")
try:
output = json.loads(output)
if 'cursor' in output:
cursor = output['cursor']
cursor_save(cursor)
cursor = None
send_mail('DROPBOX EVENTS: initialized', '<html><body><h1>Initialized cursor, no data available right now.<h1><p>This should happen only the first time you run the app.</p></body></html>')
else:
logging.error("failed decoding last cursor json from Dropbox: " + str(output))
except:
logging.exception("failed decoding last cursor data from Dropbox: " + str(output))
except:
logging.exception("failed getting last cursor from Dropbox")
return cursor
# @see https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-continue
def updates_fetch(cursor):
try:
cmd = COMMAND_LIST_FOLDER_CONTINUE.replace('{TOKEN}', DROPBOX_APP_TOKEN).replace('{CURSOR}', cursor)
print("Fetching " + cmd + ' ...')
output = subprocess.check_output(cmd, shell=True).decode("utf-8")
try:
output = json.loads(output)
if 'cursor' in output and 'entries' in output:
return output
elif 'error' in output and 'retry_after' in output['error']:
logging.debug('sleep requested... ' + str(output))
time.sleep(output['error']['retry_after'])
return {'entries': [], 'cursor': cursor, 'has_more': True};
else:
logging.error("failed decoding updates json from Dropbox: " + str(output))
except:
logging.exception("failed decoding updates data from Dropbox: " + str(output))
except:
logging.exception("failed getting updates data from Dropbox")
return None
def main():
cursor = cursor_fetch()
page = 0
limited = True
while cursor and limited:
entries = {}
cont = True
loop = 0
while cont:
loop = loop + 1
data = updates_fetch(cursor)
cont = False
if data and 'entries' in data:
print("Got data for " + str(len(data['entries'])) + " entries (#" + str(loop) + ") ...")
for e in data['entries']:
entries[e['path_lower']] = e
entries[e['path_lower']]['count'] = 0
if data['has_more'] and loop < LOOP_MAX and len(entries) < RECORDS_MAX:
cursor = data['cursor']
time.sleep(5)
cont = True
if not data['has_more'] or loop >= LOOP_MAX:
limited = False
if data and 'cursor' in data:
prev = False
for path in sorted(entries):
#if prev and (entries[path]['.tag'] == 'file' or entries[path]['.tag'] == 'folder') and path.startswith(prev):
if prev and entries[path]['.tag'] == 'file' and path.startswith(prev):
entries[prev]['count'] = entries[prev]['count'] + 1
del entries[path]
elif entries[path]['.tag'] == 'folder':
prev = path
html = '<html><body>'
html += '<h1>Dropbox recent events</h1>\n'
html += '<ul>\n'
for path in sorted(entries):
e = entries[path]
html += '<li>[' + e['.tag'] + '] <a href="https://www.dropbox.com/home' + e['path_lower'] + '" target="_blank">' + e['path_display'] + (' (+ ' + str(e['count']) + ' items)' if e['count'] else '') + '</a></li>\n'
html += '</ul>\n'
html += '<p>Full event feed: <a href="https://www.dropbox.com/events" target="_blank">https://www.dropbox.com/events</a></p>\n'
html += '<h3>Raw data (last)</h3>\n'
html += '<code>' + str(data) + '</code>\n'
html += '<h3>Old cursor</h3><code>' + cursor + '</code><h3>New cursor</h3><code>' + data['cursor'] + '</code>\n'
send_mail('Dropbox recent events' + ('' if not page else (' (' + str(page) + ')')), html)
cursor_save(data['cursor'])
if limited:
page = page + 1
print('Too much records for a single mail, sent a partial mail and continue with next records...')
if __name__ == '__main__':
main()