11from dataclasses import asdict
2+ from typing import List
23
34from db import db
45from journalist_app import utils
@@ -69,6 +70,7 @@ def process(self, event: Event, minor: int) -> EventResult:
6970 EventType .SOURCE_CONVERSATION_DELETED : self .handle_source_conversation_deleted ,
7071 EventType .SOURCE_STARRED : self .handle_source_starred ,
7172 EventType .SOURCE_UNSTARRED : self .handle_source_unstarred ,
73+ EventType .SOURCE_CONVERSATION_TRUNCATED : self .handle_source_conversation_truncated ,
7274 }[event .type ]
7375 except KeyError :
7476 return EventResult (
@@ -118,12 +120,18 @@ def handle_item_deleted(event: Event, minor: int) -> EventResult:
118120 status = (EventStatusCode .Gone , None ),
119121 )
120122
121- utils .delete_file_object (item )
122- return EventResult (
123- event_id = event .id ,
124- status = (EventStatusCode .OK , None ),
125- items = {event .target .item_uuid : None },
126- )
123+ try :
124+ utils .delete_file_object (item )
125+ return EventResult (
126+ event_id = event .id ,
127+ status = (EventStatusCode .OK , None ),
128+ items = {event .target .item_uuid : None },
129+ )
130+ except ValueError as exc :
131+ return EventResult (
132+ event_id = event .id ,
133+ status = (EventStatusCode .InternalServerError , str (exc )),
134+ )
127135
128136 @staticmethod
129137 def handle_reply_sent (event : Event , minor : int ) -> EventResult :
@@ -174,13 +182,19 @@ def handle_source_deleted(event: Event, minor: int) -> EventResult:
174182 # Mark as deleted all the items in the source's collection
175183 deleted_items = {item .uuid : None for item in source .collection }
176184
177- utils .delete_collection (source .filesystem_id )
178- return EventResult (
179- event_id = event .id ,
180- status = (EventStatusCode .OK , None ),
181- sources = {event .target .source_uuid : None },
182- items = deleted_items ,
183- )
185+ try :
186+ utils .delete_collection (source .filesystem_id )
187+ return EventResult (
188+ event_id = event .id ,
189+ status = (EventStatusCode .OK , None ),
190+ sources = {event .target .source_uuid : None },
191+ items = deleted_items ,
192+ )
193+ except ValueError as exc :
194+ return EventResult (
195+ event_id = event .id ,
196+ status = (EventStatusCode .InternalServerError , str (exc )),
197+ )
184198
185199 @staticmethod
186200 def handle_source_conversation_deleted (event : Event , minor : int ) -> EventResult :
@@ -208,6 +222,7 @@ def handle_source_conversation_deleted(event: Event, minor: int) -> EventResult:
208222 # Mark as deleted all the items in the source's collection
209223 deleted_items = {item .uuid : None for item in source .collection }
210224
225+ # NB. Does not raise exceptions from `utils.delete_file_object()`.
211226 utils .delete_source_files (source .filesystem_id )
212227 db .session .refresh (source )
213228
@@ -218,6 +233,51 @@ def handle_source_conversation_deleted(event: Event, minor: int) -> EventResult:
218233 items = deleted_items ,
219234 )
220235
236+ @staticmethod
237+ def handle_source_conversation_truncated (event : Event , minor : int ) -> EventResult :
238+ """
239+ A `source_conversation_truncated` event involves deleting all the items
240+ in the source's collection with interaction counts less than or equal to
241+ the specified upper bound, assumed to be the last item known to the
242+ client. This achieves the same consistency as a
243+ `source_conversation_deleted` event without requiring its strict
244+ versioning.
245+ """
246+
247+ try :
248+ source = Source .query .filter (Source .uuid == event .target .source_uuid ).one ()
249+ except NoResultFound :
250+ return EventResult (
251+ event_id = event .id ,
252+ status = (
253+ EventStatusCode .Gone ,
254+ None ,
255+ ),
256+ )
257+
258+ deleted : List [ItemUUID ] = []
259+ for item in source .collection :
260+ if item .interaction_count <= event .data .upper_bound :
261+ try :
262+ utils .delete_file_object (item )
263+ except ValueError :
264+ # `utils.delete_file_object()` is non-atomic: it guarantees
265+ # database deletion but not filesystem deletion. The former
266+ # is all we need for consistency with the client, and the
267+ # latter will be caught by monitoring for "disconnected"
268+ # submissions.
269+ pass
270+
271+ deleted .append (item .uuid )
272+
273+ db .session .refresh (source )
274+ return EventResult (
275+ event_id = event .id ,
276+ status = (EventStatusCode .OK , None ),
277+ sources = {source .uuid : source },
278+ items = {item_uuid : None for item_uuid in deleted },
279+ )
280+
221281 @staticmethod
222282 def handle_source_starred (event : Event , minor : int ) -> EventResult :
223283 try :
0 commit comments