@@ -391,119 +391,3 @@ pub enum ActionPriority {
391391 /// Critical priority - requires immediate attention
392392 Critical ,
393393}
394-
395- use ferroid:: { Base32SnowExt , SnowflakeGeneratorAsyncTokioExt , SnowflakeMastodonId } ;
396- use std:: fmt;
397- use std:: sync:: OnceLock ;
398-
399- /// Wrapper type for Snowflake IDs with proper serde support
400- #[ repr( transparent) ]
401- #[ derive( Debug , Clone , Copy , PartialEq , Eq , PartialOrd , Ord , Hash ) ]
402- pub struct SnowflakePosition ( pub SnowflakeMastodonId ) ;
403-
404- impl SnowflakePosition {
405- /// Create a new snowflake position
406- pub fn new ( id : SnowflakeMastodonId ) -> Self {
407- Self ( id)
408- }
409- }
410-
411- impl fmt:: Display for SnowflakePosition {
412- fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
413- // Use the efficient base32 encoding via Display
414- write ! ( f, "{}" , self . 0 )
415- }
416- }
417-
418- impl FromStr for SnowflakePosition {
419- type Err = String ;
420-
421- fn from_str ( s : & str ) -> core:: result:: Result < Self , Self :: Err > {
422- // Try parsing as base32 first
423- if let Ok ( id) = SnowflakeMastodonId :: decode ( s) {
424- return Ok ( Self ( id) ) ;
425- }
426-
427- // Fall back to parsing as raw u64
428- s. parse :: < u64 > ( )
429- . map ( |raw| Self ( SnowflakeMastodonId :: from_raw ( raw) ) )
430- . map_err ( |e| format ! ( "Failed to parse snowflake as base32 or u64: {}" , e) )
431- }
432- }
433-
434- impl Serialize for SnowflakePosition {
435- fn serialize < S > ( & self , serializer : S ) -> core:: result:: Result < S :: Ok , S :: Error >
436- where
437- S : serde:: Serializer ,
438- {
439- // Serialize as string using Display
440- serializer. serialize_str ( & self . to_string ( ) )
441- }
442- }
443-
444- impl < ' de > Deserialize < ' de > for SnowflakePosition {
445- fn deserialize < D > ( deserializer : D ) -> core:: result:: Result < Self , D :: Error >
446- where
447- D : serde:: Deserializer < ' de > ,
448- {
449- // Deserialize from string and parse
450- let s = String :: deserialize ( deserializer) ?;
451- s. parse :: < Self > ( ) . map_err ( serde:: de:: Error :: custom)
452- }
453- }
454-
455- /// Type alias for the Snowflake generator we're using
456- type SnowflakeGen = ferroid:: AtomicSnowflakeGenerator < SnowflakeMastodonId , ferroid:: MonotonicClock > ;
457-
458- /// Global ID generator for message positions using Snowflake IDs
459- /// This provides distributed, monotonic IDs that work across processes
460- static MESSAGE_POSITION_GENERATOR : OnceLock < SnowflakeGen > = OnceLock :: new ( ) ;
461-
462- pub fn get_position_generator ( ) -> & ' static SnowflakeGen {
463- MESSAGE_POSITION_GENERATOR . get_or_init ( || {
464- // Use machine ID 0 for now - in production this would be configurable
465- let clock = ferroid:: MonotonicClock :: with_epoch ( ferroid:: TWITTER_EPOCH ) ;
466- ferroid:: AtomicSnowflakeGenerator :: new ( 0 , clock)
467- } )
468- }
469-
470- /// Get the next message position synchronously
471- ///
472- /// This is designed for use in synchronous contexts like Default impls.
473- /// In practice, we don't generate messages fast enough to hit the sequence
474- /// limit (65536/ms), so Pending should rarely happen in production.
475- ///
476- /// When the sequence is exhausted (e.g., in parallel tests), this will block
477- /// briefly until the next millisecond boundary to get a fresh sequence.
478- pub fn get_next_message_position_sync ( ) -> SnowflakePosition {
479- use ferroid:: IdGenStatus ;
480-
481- let generator = get_position_generator ( ) ;
482-
483- loop {
484- match generator. next_id ( ) {
485- IdGenStatus :: Ready { id } => return SnowflakePosition :: new ( id) ,
486- IdGenStatus :: Pending { yield_for } => {
487- // If yield_for is 0, we're at the sequence limit but still in the same millisecond.
488- // Wait at least 1ms to roll over to the next millisecond and reset the sequence.
489- let wait_ms = yield_for. max ( 1 ) as u64 ;
490- std:: thread:: sleep ( std:: time:: Duration :: from_millis ( wait_ms) ) ;
491- // Loop will retry after the wait
492- }
493- }
494- }
495- }
496-
497- /// Get the next message position as a Snowflake ID (async version)
498- pub async fn get_next_message_position ( ) -> SnowflakePosition {
499- let id = get_position_generator ( )
500- . try_next_id_async ( )
501- . await
502- . expect ( "for now we are assuming this succeeds" ) ;
503- SnowflakePosition :: new ( id)
504- }
505-
506- /// Get the next message position as a String (for database storage)
507- pub async fn get_next_message_position_string ( ) -> String {
508- get_next_message_position ( ) . await . to_string ( )
509- }
0 commit comments