-
Notifications
You must be signed in to change notification settings - Fork 0
/
LockServer.py
185 lines (124 loc) · 5.41 KB
/
LockServer.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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# -*- coding: utf-8 -*-
"""
Created on Thu Dec 11 22:33:44 2017
@author: Nikita Juyal
"""
import atexit
import datetime
import logging
import web
import utils
import collections
import shelve
import random
Lock = collections.namedtuple('Lock', 'lock_id granted last_used')
class LockServer: # lock server will handle locking of files
def GET(self, filepath):
#Return server with directory in which filepath is located.
#filepath is the actual file path
web.header('Content-Type', 'text/plain; charset=UTF-8')
filepath = str(filepath)
i = web.input()
if filepath == '/': # To print dirs/lock
return '\n'.join('%s=(%s, %s)' % (filepath,
str(_locks[filepath].granted), # list of files granted
str(_locks[filepath].last_used),) # list of last used file
for filepath in sorted(_locks))
elif filepath not in _locks and 'lock_id' not in i:
return 'OK' # Since no locks, just return OK
elif 'lock_id' in i:
lock = _locks.get(filepath, -1) # If lock_id is requested and filepath is locked
try:
if int(i['lock_id']) == lock.lock_id:
_update_lock(filepath) #update last used file
return 'OK'
else:
raise Exception("Bad lock_id") #Error
except (Exception, ValueError) as e:
# logging exception(e)
_revoke_lock(filepath)
raise web.conflict()
elif _lock_expired(filepath):
_revoke_lock(filepath)
return 'OK'
# IF its already locked, or wrong lock_id- error
raise web.conflict()
def POST(self, filepath):
web.header('Content-Type', 'text/plain; charset=UTF-8')
filepath = str(filepath)
if filepath == '/':
granted_locks = {}
for filepath in web.data().split('\n'):
if not filepath:
continue
try:
granted_locks[filepath] = _grant_new_lock(filepath)
except Exception as e:
logging.exception(e)
for filepath in granted_locks: #revoke all reviously allocated locks
_revoke_lock(filepath)
raise web.unauthorized()
return '\n'.join('%s=%d' % (filepath, lock_id,) #list file name, lock id
for filepath, lock_id in granted_locks.items())
try:
return _grant_new_lock(filepath)
except Exception as e:
logging.exception(e)
raise web.unauthorized()
def DELETE(self, filepath):
web.header('Content-Type', 'text/plain; charset=UTF-8')
filepath = str(filepath)
i = web.input()
# allow deletion of multiple locks
if filepath == '/':
if 'filepaths' not in i or 'lock_ids' not in i:
raise web.badrequest()
#revoke locks if filepath =/
for filepath, lock_id in zip(i['filepaths'].split('\n'), i['lock_ids'].split('\n')):
if _locks[filepath].lock_id == int(lock_id):
_revoke_lock(filepath)
return 'OK'
elif filepath in _locks:
if 'lock_id' in i:
lock_id = i['lock_id']
if _locks[filepath].lock_id == int(lock_id):
_revoke_lock(filepath)
return 'OK'
raise web.badrequest()
else:
return 'OK'
def _lock_expired(filepath): # If lock has expired
last_used = _locks[filepath].last_used
return (datetime.datetime.now() - last_used).seconds> _config['lock_lifetime']
def _grant_new_lock(filepath): # If new lock can be created. Also revokes the older lock and creates a new one in place
if filepath in _locks:
if not _lock_expired(filepath):
#Lock has not expired
raise Exception('Unable to grant a new lock (%s).' % filepath)
_revoke_lock(filepath)
return _new_lock(filepath)
def _new_lock(filepath): # creates new lock and returns lock id
lock_id = random.randrange(0, 32068)
logging.info('Granting lock (%d) on %s.', lock_id, filepath)
t = datetime.datetime.now() # putting timestamp
_locks[filepath] = Lock(lock_id, t, t)
return lock_id
def _update_lock(filepath): #put new timestamp on last_used field
t = datetime.datetime.now()
logging.info('Update lock on %s from %s to %s.',
filepath, _locks[filepath].last_used, t)
l = _locks[filepath]
l = Lock(l.lock_id, l.granted, t)
_locks[filepath] = l
def _revoke_lock(filepath): # Revoke lock
if filepath in _locks:
logging.info('Revoking lock on %s.', filepath)
del _locks[filepath]
_config = {
'dbfile': 'locks.db',
'lock_lifetime': 60,
}
logging.info('Loading config file lockserver.dfs.json.')
utils.load_config(_config, 'lockserver.dfs.json')
_locks = shelve.open(_config['dbfile'])
atexit.register(lambda: _locks.close())