@@ -8,7 +8,7 @@ use sea_orm::DatabaseConnection;
88use tracing:: { info, warn} ;
99
1010use crate :: {
11- callrecord:: { CallRecord , CallRecordHook } ,
11+ callrecord:: { CallRecord , CallRecordFormatter , CallRecordHook } ,
1212 config:: SipFlowUploadConfig ,
1313 sipflow:: SipFlowBackend ,
1414} ;
@@ -17,6 +17,7 @@ pub struct SipFlowUploadHook {
1717 pub backend : Arc < dyn SipFlowBackend > ,
1818 pub upload_config : SipFlowUploadConfig ,
1919 pub db : Option < DatabaseConnection > ,
20+ pub formatter : Arc < dyn CallRecordFormatter > ,
2021}
2122
2223#[ async_trait]
@@ -29,22 +30,31 @@ impl CallRecordHook for SipFlowUploadHook {
2930 let backend = self . backend . clone ( ) ;
3031 let upload_config = self . upload_config . clone ( ) ;
3132 let db = self . db . clone ( ) ;
33+ let formatter = self . formatter . clone ( ) ;
3234 let call_id = record. call_id . clone ( ) ;
3335 let start = Local . from_utc_datetime ( & record. start_time . naive_utc ( ) ) ;
3436 let end = Local . from_utc_datetime ( & record. end_time . naive_utc ( ) ) ;
3537 let duration_secs = ( record. end_time - record. start_time ) . num_seconds ( ) as i32 ;
36- let date_prefix = record. start_time . format ( "%Y%m%d" ) . to_string ( ) ;
38+
39+ let media_key = formatter. format_sipflow_media_key ( record) ;
40+ let signaling_key = formatter. format_sipflow_signaling_key ( record) ;
41+ let media_file_name = formatter. format_sipflow_media_file_name ( record) ;
42+ let signaling_file_name = formatter. format_sipflow_signaling_file_name ( record) ;
3743
3844 crate :: utils:: spawn ( async move {
3945 crate :: callrecord:: sipflow_upload:: do_upload (
4046 backend,
4147 upload_config,
48+ formatter,
4249 db,
4350 & call_id,
4451 start,
4552 end,
4653 duration_secs,
47- & date_prefix,
54+ & media_key,
55+ & signaling_key,
56+ & media_file_name,
57+ & signaling_file_name,
4858 )
4959 . await ;
5060 } ) ;
@@ -53,15 +63,20 @@ impl CallRecordHook for SipFlowUploadHook {
5363 }
5464}
5565
66+ #[ allow( clippy:: too_many_arguments) ]
5667async fn do_upload (
5768 backend : Arc < dyn SipFlowBackend > ,
5869 upload_config : SipFlowUploadConfig ,
70+ _formatter : Arc < dyn CallRecordFormatter > ,
5971 db : Option < DatabaseConnection > ,
6072 call_id : & str ,
6173 start : DateTime < Local > ,
6274 end : DateTime < Local > ,
6375 duration_secs : i32 ,
64- date_prefix : & str ,
76+ media_key : & str ,
77+ signaling_key : & str ,
78+ media_file_name : & str ,
79+ signaling_file_name : & str ,
6580) {
6681 if let Err ( e) = backend. flush ( ) . await {
6782 warn ! ( call_id, "SipFlowUploadHook: flush failed: {e}" ) ;
@@ -81,7 +96,8 @@ async fn do_upload(
8196 call_id,
8297 start,
8398 end,
84- date_prefix,
99+ media_key,
100+ media_file_name,
85101 db. as_ref ( ) ,
86102 duration_secs,
87103 )
@@ -94,18 +110,28 @@ async fn do_upload(
94110 } ;
95111
96112 if signaling {
97- upload_signaling_flow ( & upload_config, backend. as_ref ( ) , call_id, start, end, date_prefix)
98- . await ;
113+ upload_signaling_flow (
114+ & upload_config,
115+ backend. as_ref ( ) ,
116+ call_id,
117+ start,
118+ end,
119+ signaling_key,
120+ signaling_file_name,
121+ )
122+ . await ;
99123 }
100124}
101125
126+ #[ allow( clippy:: too_many_arguments) ]
102127async fn upload_media (
103128 backend : & dyn SipFlowBackend ,
104129 upload_config : & SipFlowUploadConfig ,
105130 call_id : & str ,
106131 start : DateTime < Local > ,
107132 end : DateTime < Local > ,
108- date_prefix : & str ,
133+ media_key : & str ,
134+ media_file_name : & str ,
109135 db : Option < & DatabaseConnection > ,
110136 duration_secs : i32 ,
111137) {
@@ -121,8 +147,6 @@ async fn upload_media(
121147 return ;
122148 }
123149
124- let key = format ! ( "{}/{}.wav" , date_prefix, call_id) ;
125-
126150 let wav_len = wav_bytes. len ( ) ;
127151 let url_result = match upload_config {
128152 SipFlowUploadConfig :: S3 {
@@ -136,9 +160,9 @@ async fn upload_media(
136160 ..
137161 } => {
138162 let full_key = if root. is_empty ( ) {
139- key . clone ( )
163+ media_key . to_string ( )
140164 } else {
141- format ! ( "{}/{}" , root. trim_end_matches( '/' ) , key )
165+ format ! ( "{}/{}" , root. trim_end_matches( '/' ) , media_key )
142166 } ;
143167 upload_s3 (
144168 vendor, bucket, region, access_key, secret_key, endpoint, & full_key, wav_bytes,
@@ -154,7 +178,7 @@ async fn upload_media(
154178 } )
155179 }
156180 SipFlowUploadConfig :: Http { url, headers, .. } => {
157- upload_http ( url, headers. as_ref ( ) , call_id , wav_bytes) . await
181+ upload_http ( url, headers. as_ref ( ) , media_file_name , wav_bytes) . await
158182 }
159183 } ;
160184
@@ -188,13 +212,15 @@ async fn upload_media(
188212 }
189213}
190214
215+ #[ allow( clippy:: too_many_arguments) ]
191216async fn upload_signaling_flow (
192217 upload_config : & SipFlowUploadConfig ,
193218 backend : & dyn SipFlowBackend ,
194219 call_id : & str ,
195220 start : DateTime < Local > ,
196221 end : DateTime < Local > ,
197- date_prefix : & str ,
222+ signaling_key : & str ,
223+ signaling_file_name : & str ,
198224) {
199225 let flow_items = match backend. query_flow ( call_id, start, end) . await {
200226 Ok ( items) => items,
@@ -211,8 +237,6 @@ async fn upload_signaling_flow(
211237 let jsonl = crate :: sipflow:: SipFlowQuery :: export_jsonl ( & flow_items) ;
212238 let data = jsonl. into_bytes ( ) ;
213239
214- let key = format ! ( "{}/{}.jsonl" , date_prefix, call_id) ;
215-
216240 let result = match upload_config {
217241 SipFlowUploadConfig :: S3 {
218242 vendor,
@@ -225,17 +249,17 @@ async fn upload_signaling_flow(
225249 ..
226250 } => {
227251 let full_key = if root. is_empty ( ) {
228- key . clone ( )
252+ signaling_key . to_string ( )
229253 } else {
230- format ! ( "{}/{}" , root. trim_end_matches( '/' ) , key )
254+ format ! ( "{}/{}" , root. trim_end_matches( '/' ) , signaling_key )
231255 } ;
232256 upload_s3 (
233257 vendor, bucket, region, access_key, secret_key, endpoint, & full_key, data,
234258 )
235259 . await
236260 }
237261 SipFlowUploadConfig :: Http { url, headers, .. } => {
238- upload_http_jsonl ( url, headers. as_ref ( ) , call_id , data) . await
262+ upload_http_jsonl ( url, headers. as_ref ( ) , signaling_file_name , data) . await
239263 }
240264 } ;
241265
@@ -279,13 +303,12 @@ async fn upload_s3(
279303async fn upload_http (
280304 url : & str ,
281305 headers : Option < & std:: collections:: HashMap < String , String > > ,
282- call_id : & str ,
306+ file_name : & str ,
283307 data : Vec < u8 > ,
284308) -> Result < String > {
285309 let client = reqwest:: Client :: new ( ) ;
286- let file_name = format ! ( "{}.wav" , call_id) ;
287310 let part = reqwest:: multipart:: Part :: bytes ( data)
288- . file_name ( file_name)
311+ . file_name ( file_name. to_string ( ) )
289312 . mime_str ( "audio/wav" ) ?;
290313 let form = reqwest:: multipart:: Form :: new ( ) . part ( "recording" , part) ;
291314
@@ -298,7 +321,6 @@ async fn upload_http(
298321 let response = req. send ( ) . await ?;
299322 if response. status ( ) . is_success ( ) {
300323 let body = response. text ( ) . await . unwrap_or_default ( ) ;
301- // Return a URL: use the posted URL or parse from response if it looks like one.
302324 let recording_url = if body. starts_with ( "http" ) {
303325 body. trim ( ) . to_string ( )
304326 } else {
@@ -317,13 +339,12 @@ async fn upload_http(
317339async fn upload_http_jsonl (
318340 url : & str ,
319341 headers : Option < & std:: collections:: HashMap < String , String > > ,
320- call_id : & str ,
342+ file_name : & str ,
321343 data : Vec < u8 > ,
322344) -> Result < ( ) > {
323345 let client = reqwest:: Client :: new ( ) ;
324- let file_name = format ! ( "{}.jsonl" , call_id) ;
325346 let part = reqwest:: multipart:: Part :: bytes ( data)
326- . file_name ( file_name)
347+ . file_name ( file_name. to_string ( ) )
327348 . mime_str ( "application/jsonl" ) ?;
328349 let form = reqwest:: multipart:: Form :: new ( ) . part ( "signaling" , part) ;
329350
@@ -350,13 +371,12 @@ async fn upload_http_jsonl(
350371#[ cfg( test) ]
351372mod tests {
352373 use super :: * ;
374+ use crate :: callrecord:: DefaultCallRecordFormatter ;
353375 use crate :: sipflow:: { SipFlowBackend , SipFlowItem , SipFlowMediaStats } ;
354376 use chrono:: { DateTime , Local } ;
355377
356378 struct MockBackend {
357- /// WAV bytes returned by query_media
358379 media : Vec < u8 > ,
359- /// Track flush call count
360380 flush_count : std:: sync:: Arc < std:: sync:: atomic:: AtomicUsize > ,
361381 }
362382
@@ -429,17 +449,14 @@ mod tests {
429449 media : None ,
430450 } ,
431451 db : None ,
452+ formatter : Arc :: new ( DefaultCallRecordFormatter :: default ( ) ) ,
432453 } ;
433454 let mut record = make_record ( ) ;
434- // Hook returns immediately without blocking
435455 let start = std:: time:: Instant :: now ( ) ;
436456 hook. on_record_completed ( & mut record) . await . unwrap ( ) ;
437457 let elapsed = start. elapsed ( ) ;
438- // Should return in well under 100ms (no actual work done synchronously)
439458 assert ! ( elapsed. as_millis( ) < 100 , "hook blocked for {elapsed:?}" ) ;
440- // URL should NOT be set (the background task would set it later)
441459 assert ! ( record. details. recording_url. is_none( ) ) ;
442- // Give background task time to run, then check flush was called
443460 tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 100 ) ) . await ;
444461 assert_eq ! ( flush_count. load( std:: sync:: atomic:: Ordering :: Relaxed ) , 1 ) ;
445462 }
0 commit comments