3
3
"""
4
4
import json
5
5
import logging
6
- from typing import Set
6
+ from typing import List , Optional , Set
7
7
8
+ import aioredlock
8
9
from aiohttp import web
9
10
from jsonschema import ValidationError
10
11
@@ -163,14 +164,14 @@ async def get_project(request: web.Request):
163
164
project = await get_project_for_user (
164
165
request .app ,
165
166
project_uuid = project_uuid ,
166
- user_id = request [ RQT_USERID_KEY ] ,
167
+ user_id = user_id ,
167
168
include_templates = True ,
168
169
)
169
170
170
171
return {"data" : project }
171
172
except ProjectInvalidRightsError :
172
173
raise web .HTTPForbidden (
173
- reason = f"User { user_id } has no right to read { project_uuid } "
174
+ reason = f"You do not have sufficient rights to read project { project_uuid } "
174
175
)
175
176
except ProjectNotFoundError :
176
177
raise web .HTTPNotFound (reason = f"Project { project_uuid } not found" )
@@ -235,7 +236,7 @@ async def replace_project(request: web.Request):
235
236
236
237
except ProjectInvalidRightsError :
237
238
raise web .HTTPForbidden (
238
- reason = f"User { user_id } has no rights to write to project { project_uuid } "
239
+ reason = "You do not have sufficient rights to save the project"
239
240
)
240
241
except ProjectNotFoundError :
241
242
raise web .HTTPNotFound
@@ -256,19 +257,27 @@ async def delete_project(request: web.Request):
256
257
user_id = user_id ,
257
258
include_templates = True ,
258
259
)
260
+ project_users : List [int ] = []
259
261
with managed_resource (user_id , None , request .app ) as rt :
260
- other_users = await rt .find_users_of_resource ("project_id" , project_uuid )
261
- if other_users :
262
- message = "Project is opened by another user. It cannot be deleted."
263
- if user_id in other_users :
264
- message = "Project is still open. It cannot be deleted until it is closed."
265
- # we cannot delete that project
266
- raise web .HTTPForbidden (reason = message )
262
+ project_users = await rt .find_users_of_resource ("project_id" , project_uuid )
263
+ if project_users :
264
+ # that project is still in use
265
+ if user_id in project_users :
266
+ message = "Project is still open in another tab/browser. It cannot be deleted until it is closed."
267
+ else :
268
+ other_users = set (project_users )
269
+ other_user_names = {
270
+ await get_user_name (request .app , x ) for x in other_users
271
+ }
272
+ message = f"Project is open by { other_user_names } . It cannot be deleted until the project is closed."
273
+
274
+ # we cannot delete that project
275
+ raise web .HTTPForbidden (reason = message )
267
276
268
277
await projects_api .delete_project (request , project_uuid , user_id )
269
278
except ProjectInvalidRightsError :
270
279
raise web .HTTPForbidden (
271
- reason = f"User { user_id } has no rights to delete project"
280
+ reason = "You do not have sufficient rights to delete this project"
272
281
)
273
282
except ProjectNotFoundError :
274
283
raise web .HTTPNotFound (reason = f"Project { project_uuid } not found" )
@@ -289,31 +298,41 @@ async def open_project(request: web.Request) -> web.Response:
289
298
project_uuid = request .match_info .get ("project_id" )
290
299
client_session_id = await request .json ()
291
300
try :
292
- with managed_resource (user_id , client_session_id , request .app ) as rt :
293
- # TODO: temporary hidden until get_handlers_from_namespace refactor to seek marked functions instead!
294
- from .projects_api import get_project_for_user
295
-
296
- project = await get_project_for_user (
297
- request .app ,
298
- project_uuid = project_uuid ,
299
- user_id = user_id ,
300
- include_templates = True ,
301
- )
301
+ # TODO: temporary hidden until get_handlers_from_namespace refactor to seek marked functions instead!
302
+ from .projects_api import get_project_for_user
302
303
303
- # let's check if that project is already opened by someone else
304
- other_users : Set [ int ] = {
305
- x
306
- for x in await rt . find_users_of_resource ( "project_id" , project_uuid )
307
- if x != f" { user_id } "
308
- }
304
+ project = await get_project_for_user (
305
+ request . app ,
306
+ project_uuid = project_uuid ,
307
+ user_id = user_id ,
308
+ include_templates = True ,
309
+ )
309
310
310
- if other_users :
311
- # project is already locked
312
- usernames = [
313
- await get_user_name (request .app , uid ) for uid in other_users
314
- ]
315
- raise HTTPLocked (reason = f"Project is already opened by { usernames } " )
316
- await rt .add ("project_id" , project_uuid )
311
+ async def try_add_project () -> Optional [Set [int ]]:
312
+ with managed_resource (user_id , client_session_id , request .app ) as rt :
313
+ try :
314
+ async with await rt .get_registry_lock ():
315
+ other_users : Set [int ] = {
316
+ x
317
+ for x in await rt .find_users_of_resource (
318
+ "project_id" , project_uuid
319
+ )
320
+ if x != user_id
321
+ }
322
+
323
+ if other_users :
324
+ return other_users
325
+ await rt .add ("project_id" , project_uuid )
326
+ except aioredlock .LockError :
327
+ # TODO: this lock is not a good solution for long term
328
+ # maybe a project key in redis might improve spped of checking
329
+ raise HTTPLocked (reason = "Project is locked" )
330
+
331
+ other_users = await try_add_project ()
332
+ if other_users :
333
+ # project is already locked
334
+ usernames = [await get_user_name (request .app , uid ) for uid in other_users ]
335
+ raise HTTPLocked (reason = f"Project is already opened by { usernames } " )
317
336
318
337
# user id opened project uuid
319
338
await projects_api .start_project_interactive_services (request , project , user_id )
@@ -381,18 +400,14 @@ async def _close_project_task() -> None:
381
400
async def state_project (request : web .Request ) -> web .Response :
382
401
user_id = request [RQT_USERID_KEY ]
383
402
project_uuid = request .match_info .get ("project_id" )
384
- with managed_resource (user_id , None , request .app ) as rt :
385
- # TODO: temporary hidden until get_handlers_from_namespace refactor to seek marked functions instead!
386
- from .projects_api import get_project_for_user
387
-
388
- # check that project exists
389
- await get_project_for_user (
390
- request .app ,
391
- project_uuid = project_uuid ,
392
- user_id = user_id ,
393
- include_templates = True ,
394
- )
403
+ # TODO: temporary hidden until get_handlers_from_namespace refactor to seek marked functions instead!
404
+ from .projects_api import get_project_for_user
395
405
406
+ # check that project exists
407
+ await get_project_for_user (
408
+ request .app , project_uuid = project_uuid , user_id = user_id , include_templates = True ,
409
+ )
410
+ with managed_resource (user_id , None , request .app ) as rt :
396
411
users_of_project = await rt .find_users_of_resource ("project_id" , project_uuid )
397
412
usernames = [
398
413
await get_user_name (request .app , uid ) for uid in set (users_of_project )
@@ -416,19 +431,20 @@ async def get_active_project(request: web.Request) -> web.Response:
416
431
417
432
try :
418
433
project = None
434
+ user_active_projects = []
419
435
with managed_resource (user_id , client_session_id , request .app ) as rt :
420
436
# get user's projects
421
- list_project_ids = await rt .find ("project_id" )
422
- if list_project_ids :
423
- # TODO: temporary hidden until get_handlers_from_namespace refactor to seek marked functions instead!
424
- from .projects_api import get_project_for_user
425
-
426
- project = await get_project_for_user (
427
- request .app ,
428
- project_uuid = list_project_ids [0 ],
429
- user_id = user_id ,
430
- include_templates = True ,
431
- )
437
+ user_active_projects = await rt .find ("project_id" )
438
+ if user_active_projects :
439
+ # TODO: temporary hidden until get_handlers_from_namespace refactor to seek marked functions instead!
440
+ from .projects_api import get_project_for_user
441
+
442
+ project = await get_project_for_user (
443
+ request .app ,
444
+ project_uuid = user_active_projects [0 ],
445
+ user_id = user_id ,
446
+ include_templates = True ,
447
+ )
432
448
433
449
return web .json_response ({"data" : project })
434
450
except ProjectNotFoundError :
0 commit comments