
    *Qj                    d    d Z ddlmZ ddlZ ej                  d      ZdZdZd
dZ G d d	      Z	y)u  Routine scheduler — fires time-based routines server-side (APScheduler).

Phase-2 routines have two kinds of trigger:
  * TIME (or the time half of BOTH) — fires from this gateway on a cron, whether
    or not the phone is even awake. That's what this module owns.
  * PLACE (or the place half of BOTH) — an on-device geofence; the phone sends a
    ``new_location`` event naming the matched place, which ``main`` matches to
    routines directly. Nothing here.

``RoutineScheduler`` loads routines from the phone store on startup, schedules a
cron job per time/both routine, and calls the injected ``dispatch(routine)``
coroutine when one fires. ``reload()`` is re-run after any routine
create/cancel so the live schedule always matches storage.

APScheduler is imported LAZILY inside ``start`` so ``import scheduler`` (and the
test suite) works with no event loop and even if the package is missing until
runtime. Tests inject a fake scheduler backend via ``sched=`` and never import
APScheduler.
    )annotationsNzjarvis.gateway.scheduler)montuewedthufrisatsunzAsia/Riyadhc                   t        | t              sy| j                  d      dvryt        | j                  d      xs d      j	                         }d|vry|j                  d      \  }}}	 t        |      }t        |      }d|cxk  rdk  rn yd|cxk  rd	k  sy y| j                  d
      xs g }g }t        |t              rP|D ]K  }	t        |	      j	                         j                         dd }
|
t        v s6|
|vs;|j                  |
       M |||rdj                  |      dS ddS # t        $ r Y yw xY w)a,  Turn a routine trigger into cron fields, or None if it isn't time-based.

    Returns ``{"hour", "minute", "day_of_week"}`` where ``day_of_week`` is a
    comma list ('mon,wed') or None for every day. Place-only triggers, or
    malformed times, return None (the routine simply isn't time-scheduled).Ntype)timebothat :r      ;   days   ,)hourminuteday_of_week)
isinstancedictgetstrstrip	partitionint
ValueErrorlistlower	_WEEKDAYSappendjoin)triggerr   hh_mmr   r   r   pickeddkeys              ?/Users/ahmed/devFolder/Ultron/claude-voice/gateway/scheduler.py
parse_cronr/      sP    gt${{6"22	W[[$"	%	+	+	-B
"}S!IB22wR OO !"V 1r 1 !2;;v$"DF$Aa&,,.&&(!,CiCv$5c"  F/5388F+A A;?A A  s   -D1 1	D=<D=c                  H    e Zd ZdZedf	 d	dZd
dZd
dZddZddZ	ddZ
y)RoutineScheduleruH  Owns the APScheduler AsyncIOScheduler and the routine→cron-job mapping.

    ``dispatch``       async callable invoked with the routine dict when it fires.
    ``list_routines``  async callable returning the current list of routine dicts.
    ``sched``          optional pre-built scheduler backend (tests inject a fake).
    Nc                <    || _         || _        || _        || _        y )N)dispatchlist_routinestz_sched)selfr3   r4   r5   scheds        r.   __init__zRoutineScheduler.__init__G   s     *    c                   K   | j                   ddlm}  || j                        | _         t	        | j                   dd      s| j                   j                          | j                          d{   S 7 w)z>Build+start the scheduler (if not injected) and load routines.Nr   )AsyncIOScheduler)timezonerunningF)r6   apscheduler.schedulers.asyncior<   r5   getattrstartreload)r7   r<   s     r.   rA   zRoutineScheduler.startN   sU     ;;G*DGG<DKt{{Iu5KK[[]"""s   A/A81A62A8c                  K   | j                   yt        | j                   j                               D ]  }|j                           	 | j	                          d{   }d}|xs g D ]:  }t        |j                  d      xs i       }|s$| j                  ||       |dz  }< t        j                  d|       |S 7 a# t
        $ r,}t        j                  dt        |      dd        Y d}~yd}~ww xY ww)u   Rebuild all jobs from storage. Returns the number of time-scheduled
        routines. Never raises — a storage hiccup just leaves the schedule empty.Nr   zroutine reload failed: %sx   r'      z1routine scheduler loaded %d time-based routine(s))r6   r"   get_jobsremover4   	Exceptionloggerwarningr   r/   r   _add_jobinfo)r7   jobroutinesenrcrons          r.   rB   zRoutineScheduler.reloadW   s      ;;,,./CJJL 0	!//11H RAaeeI.4"5DMM!T"FA   	GK 2 	NN6AtE	sC   AC8	C  B>C  !AC8>C   	C5	"C0+C80C55C8c                    ddl m}  ||d   |d   |d   | j                        }| j                  j	                  | j
                  ||gdt        |j                  dd	            z   d
dd
       y )Nr   )CronTriggerr   r   r   )r   r   r   r=   zroutine:namer   Ti,  )r'   argsidreplace_existingmisfire_grace_timecoalesce)apscheduler.triggers.cronrT   r5   r6   add_job_runr   r   )r7   routinerR   rT   trigs        r.   rK   zRoutineScheduler._add_jobm   sn    9VT(^'+M':TWWNIIt7)CFB 788!cD 	 	Jr:   c                   K   	 | j                  |       d {    y 7 # t        $ r<}t        j                  d|j	                  d      t        |      d d        Y d }~y d }~ww xY ww)Nz routine '%s' dispatch failed: %srU      )r3   rH   rI   rJ   r   r   )r7   r^   rO   s      r.   r]   zRoutineScheduler._runv   sX     	>--((( 	>NN=";;v.At> >	>s6   A(    A(  	A%2A A( A%%A(c                   K   | j                   6t        | j                   dd      r	 | j                   j                  d       y y y # t        $ r Y y w xY ww)Nr>   F)wait)r6   r@   shutdownrH   )r7   s    r.   rd   zRoutineScheduler.shutdown}   sU     ;;"wt{{Iu'M$$%$0 (N"  s(   $AA A	AAAA)r5   r   returnNone)re   r    )r^   r   rR   r   re   rf   )r^   r   re   rf   )re   rf   )__name__
__module____qualname____doc__
DEFAULT_TZr9   rA   rB   rK   r]   rd    r:   r.   r1   r1   ?   s7     ;E $#,J>r:   r1   )r'   r   re   zdict | None)
rj   
__future__r   logging	getLoggerrI   r$   rk   r/   r1   rl   r:   r.   <module>rp      sB   ( # 			5	6=	
A@C Cr:   