@@ -149,6 +149,7 @@ def post(self):
149149 curr_date = dds_web .utils .current_time ()
150150 delete_message = ""
151151 is_aborted = False
152+ is_queue_operation = False
152153
153154 # Moving to Available
154155 if new_status == "Available" :
@@ -169,33 +170,23 @@ def post(self):
169170 )
170171 elif new_status == "Archived" :
171172 is_aborted = json_input .get ("is_aborted" , False )
173+ is_queue_operation = True
172174 new_status_row , delete_message = self .archive_project (
173- project = project , current_time = curr_date , aborted = is_aborted
175+ project = project ,
176+ current_time = curr_date ,
177+ aborted = is_aborted ,
174178 )
175179 else :
176180 raise DDSArgumentError (message = "Invalid status" )
177181
178- try :
179- project .project_statuses .append (new_status_row )
180- project .busy = False # TODO: Use set_busy instead?
181- db .session .commit ()
182- flask .current_app .logger .info (
183- f"Busy status set. Project: '{ project .public_id } ', Busy: False"
184- )
185- except (sqlalchemy .exc .OperationalError , sqlalchemy .exc .SQLAlchemyError ) as err :
186- flask .current_app .logger .exception (err )
187- db .session .rollback ()
188- raise DatabaseError (
189- message = str (err ),
190- alt_message = (
191- "Status was not updated"
192- + (
193- ": Database malfunction."
194- if isinstance (err , sqlalchemy .exc .OperationalError )
195- else ": Server Error."
196- )
197- ),
198- ) from err
182+ return_message = ""
183+
184+ if not is_queue_operation :
185+ # If operations was not handled by a queue, commit the changes
186+ self .update_project_status (project = project , new_status_row = new_status_row )
187+ return_message += f"Project { project .public_id } updated to status { new_status } "
188+ else :
189+ return_message += f"The status of project { project .public_id } is being updated to { new_status } . The DDS is handling this in the background. It may take some time to complete."
199190
200191 # Mail users once project is made available
201192 if new_status == "Available" and send_email :
@@ -204,10 +195,6 @@ def post(self):
204195 userobj = user .researchuser , mail_type = "project_release" , project = project
205196 )
206197
207- return_message = f"{ project .public_id } updated to status { new_status } " + (
208- " (aborted)" if new_status == "Archived" and is_aborted else ""
209- )
210-
211198 if new_status != "Available" :
212199 return_message += delete_message + "."
213200 else :
@@ -235,9 +222,11 @@ def patch(self):
235222 # Get json input from request
236223 json_input = flask .request .get_json (silent = True ) # Already checked by json_required
237224
238- # the status has changed at least two times,
239- # next time the project expires it wont change again -> error
240- if project .times_expired >= 2 :
225+ # A project can have the expired status 3 times
226+ # It can be released / extended 3 times
227+ # If more attempts --> cannot be extended again
228+ # will be automatically archived by the system
229+ if project .times_expired > 3 :
241230 raise DDSArgumentError (
242231 "Project availability limit: The maximum number of changes in data availability has been reached."
243232 )
@@ -411,9 +400,10 @@ def release_project(
411400 days = deadline_in
412401 )
413402
414- # Project can only move from Expired 2 times
403+ # Project can only MOVE FROM Expired 2 times
404+ # but can BE IN Exired 3 times
415405 if project .current_status == "Expired" :
416- if project .times_expired > 2 :
406+ if project .times_expired > 3 :
417407 raise DDSArgumentError (
418408 "Project availability limit: Project cannot be made Available any more times"
419409 )
@@ -506,7 +496,10 @@ def delete_project(self, project: models.Project, current_time: datetime.datetim
506496 return models .ProjectStatuses (status = "Deleted" , date_created = current_time ), delete_message
507497
508498 def archive_project (
509- self , project : models .Project , current_time : datetime .datetime , aborted : bool = False
499+ self ,
500+ project : models .Project ,
501+ current_time : datetime .datetime ,
502+ aborted : bool = False ,
510503 ):
511504 """Archive project: Make status Archived.
512505
@@ -522,20 +515,47 @@ def archive_project(
522515 "Please abort the project if you wish to proceed."
523516 )
524517
518+ # Get redis connection to add a job if needed
519+ redis_url = flask .current_app .config .get ("REDIS_URL" )
520+ r = Redis .from_url (redis_url )
521+ q = Queue (connection = r )
522+
523+ job_delete_contents = q .enqueue (
524+ self .queue_helper_function_delete_contents ,
525+ project_id = project .public_id , # It is not possible to pass the project object directly to the queue
526+ clear_proj_info = aborted , # If aborted, clear project info
527+ )
528+
529+ job_update_db = q .enqueue (
530+ self .queue_helper_function_update_proj_status ,
531+ project_id = project .public_id ,
532+ current_time = current_time ,
533+ new_status = "Archived" ,
534+ aborted = aborted ,
535+ depends_on = job_delete_contents , # This job is only executed after success of delete contents
536+ )
537+
538+ return None , "" # Dummy returns to not break the main function
539+
540+ @dbsession
541+ def queue_helper_function_delete_contents (self , project_id : int , clear_proj_info : bool = False ):
542+ """Delete the project contents for the archiving operation.
543+ Function to be called by the queue."""
544+
545+ project = models .Project .query .filter_by (public_id = project_id ).one_or_none ()
546+
525547 try :
526548 # Deletes files (also commits session in the function - possibly refactor later)
527549 RemoveContents ().delete_project_contents (
528550 project_id = project .public_id , delete_bucket = True
529551 )
530- delete_message = f"\n All files in { project .public_id } deleted"
531552 self .rm_project_user_keys (project = project )
532553
533554 # Only mark as inactive after all deletion operations succeed
534555 project .is_active = False
535556 # Delete metadata from project row
536- if aborted :
557+ if clear_proj_info :
537558 project = self .delete_project_info (project )
538- delete_message += " and project info cleared"
539559
540560 except (TypeError , DatabaseError , DeletionError , BucketNotFoundError ) as err :
541561 flask .current_app .logger .exception (err )
@@ -546,11 +566,35 @@ def archive_project(
546566 pass_message = True ,
547567 ) from err
548568
549- return (
550- models .ProjectStatuses (
551- status = "Archived" , date_created = current_time , is_aborted = aborted
552- ),
553- delete_message ,
569+ @dbsession
570+ def queue_helper_function_update_proj_status (
571+ self ,
572+ project_id : int ,
573+ current_time : datetime .datetime ,
574+ new_status : str ,
575+ aborted : bool = False ,
576+ ):
577+ """When the delete contents operation has being sucesfully executed. Perform the update in the DB.
578+ Function ONLY to be called by the queue.
579+ """
580+
581+ project = models .Project .query .filter_by (public_id = project_id ).one_or_none ()
582+
583+ new_status_row = models .ProjectStatuses (
584+ status = new_status , date_created = current_time , is_aborted = aborted
585+ )
586+ self .update_project_status (project = project , new_status_row = new_status_row )
587+
588+ @dbsession
589+ def update_project_status (
590+ self , project : models .Project , new_status_row : models .ProjectStatuses
591+ ):
592+ """Update the project status in the database."""
593+ project .project_statuses .append (new_status_row )
594+ project .busy = False # TODO: Use set_busy instead?
595+ db .session .commit ()
596+ flask .current_app .logger .info (
597+ f"Busy status set. Project: '{ project .public_id } ', Busy: False"
554598 )
555599
556600 def rm_project_user_keys (self , project ):
0 commit comments