Ë
    5IÎj+Š  ã            	       óì  — U d Z ddlZddlZddlZddlZddlZddlZddlmZm	Z	 ddl
m
Z
 ddlmZ ddlmZ ddlZddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZmZ ddlmZmZmZmZm Z   ejB                  e"«      Z#dZ$ e%d e&d«      jO                  «       xs d«      Z(ejR                  Z)e)D � ch c]  } | jU                  «       ’Œ c} Z+dhZ,dZ-dZ.dZ/de0defd„Z1i Z2e3e0e3f   e4d<   de3fd„Z5de0de3fd„Z6d0de0de0dee7   ddfd„Z8de0ddfd„Z9de0dee3   fd„Z:de0de3fd „Z;d0dee0   de3fd!„Z<d"ede=fd#„Z>de7de7fd$„Z?d%edee
   fd&„Z@d0d%ed'd(deAfd)„ZB G d*„ d+«      ZC G d,„ d-e«      ZDde0ddfd.„ZEd1d/„ZFyc c} w )2u  
organizer_service.py â€” ingestiÃ³n de las fotos del fotÃ³grafo (lector de QR), por colegio.

Port de `organizer_service.py` de hec-photo-app. Flujo:

  1. El fotÃ³grafo sube las fotos por FTP a `incoming/{colegio}/`.
  2. El organizer (watchdog + sondeo de seguridad) las analiza en paralelo: QR de la tarjeta de la
     familia (cascada completa en `qr_detect`: puerta doble, CLAHE, Otsu, enfoque, OCR de rescate).
  3. El fotÃ³grafo se deduce del PREFIJO del archivo (`DSCâ€¦` â†’ "xavier"). Cada fotÃ³grafo tiene su propio
     "QR pegajoso" persistido en base: una foto de tarjeta lo fija; las que siguen cuelgan de Ã©l.
  4. Destino: `photos/{colegio}/{fotÃ³grafo}/{CÃ“DIGO}/` (la foto de la tarjeta, en `.qr/`).
     Prefijo desconocido o imagen ilegible â†’ `incoming/â€¦/_errors/`, sin perderla.

âš ï¸� El organizer no debe morir en silencio. Se muriÃ³ de tres maneras en HEC y cada regla de abajo sale
de ese dÃ­a (el test `test_organizer.py` las reproduce):
  - `process_pending` va dentro de `try/finally`: `processing` es su primera guarda, y una excepciÃ³n que
    lo dejaba en True hacÃ­a que TODAS las llamadas siguientes salieran de inmediato.
  - Un archivo malo no tumba el lote: el fallo de un `future` se atrapa por archivo.
  - El `while True` vive FUERA del `try` de cada pasada, con backoff exponencial: una caÃ­da de la base
    retrasa el trabajo, no lo cancela. Solo `CancelledError` (apagado) atraviesa.
  - `_move` devuelve False sin lanzar y se comprueba: si no, la foto seguÃ­a en incoming y el contador
    decÃ­a que estaba organizada.
  - El estado es POR COLEGIO: uno solo compartido dejaba `processing=True` en todos a la vez.
  - watchdog llama desde SU hilo: todo entra al event loop por `call_soon_threadsafe`.
  - El anÃ¡lisis (CPU) y la espera de estabilidad (sleep) van en hilos, no bloquean la API.

Diferencias deliberadas con HEC: sin IA anticipada ni detecciÃ³n de ojos cerrados (en este proyecto la IA
la pide la familia, con cuota), y los fotÃ³grafos/estado se guardan en la misma base que el resto, con otro esquema.
é    N)ÚThreadPoolExecutorÚas_completed)Údatetime)ÚPath)ÚOptional)ÚFileSystemEventHandler)ÚObserver)Úconfig)Úget_db)ÚutcnowÚ
utcnow_iso)Úphoto_serviceÚphotographer_serviceÚ	qr_detectÚschool_serviceÚshoot_serviceTé   Úosé   z.datg     Àr@g     À‚@g      ^@Ú	school_idÚreturnc                 óø   — 	 t        «       5 }|j                  d«      j                  «       }ddd«       r|d   | k(  rt        j                  S t        j                  | z  S # 1 sw Y   Œ6xY w# t
        $ r Y Œ*w xY w)uÂ   `incoming/{colegio}/`. La instalaciÃ³n anterior (un solo colegio) subÃ­a a la raÃ­z de `incoming/`:
    esa carpeta sigue siendo la de ese colegio para no obligar a cambiar el FTP de la cÃ¡mara.z5SELECT value FROM app_kv WHERE key='legacy_school_id'NÚvalue)r   ÚexecuteÚfetchoner
   ÚINCOMING_DIRÚ	Exception)r   ÚconnÚrows      ú5/var/www/photos/backend/services/organizer_service.pyÚincoming_dir_forr!   D   s   € ðÜ‹Xð 	c˜Ø—,‘,ÐVÓW×`Ñ`ÓbˆC÷	cá�3�w‘< 9Ò,Ü×&Ñ&Ð&ô ×Ñ Ñ*Ð*÷	cð 	cûô ò Ùðús'   ‚
A- Œ A!¬!A- Á!A*Á&A- Á-	A9Á8A9Ú_status_by_schoolc                  ó"   — ddd dddd d d d dg i dœS )NFr   )Ú
processingÚpending_countÚlast_run_atÚlast_processed_countÚlast_elapsed_msÚaliveÚ
started_atÚ
last_ok_atÚ
last_errorÚlast_error_atÚconsecutive_errorsÚfailed_filesÚsticky_by_photographer© r1   ó    r    Ú_new_statusr3   V   s,   € à¨aÀÐ^_Øà d¸$ÈdÐeiØ°Èrñð r2   c                 ó>   — t         j                  | t        «       «      S ©N)r"   Ú
setdefaultr3   ©r   s    r    Ú_status_forr8   `   s   € Ü×'Ñ'¨	´;³=ÓAÐAr2   ÚmessageÚfilesc                 óÚ   — t        | «      }||d<   t        «       |d<   |dxx   dz  cc<   |r/t        |d   «      }|d   |D �cg c]	  }||vsŒ|‘Œ c}z   dd  |d<   t        | «       y c c}w )Nr,   r-   r.   é   r/   éìÿÿÿ)r8   r   ÚsetÚ_publish_status)r   r9   r:   ÚstÚseenÚfs         r    Ú_record_errorrC   d   s‚   € Ü	�YÓ	€BØ€B€|ÑÜ$›,€B€ÑØÐÓ Ñ!ÓÙÜ�2�nÑ%Ó&ˆØ  Ñ0¸uÖ3V¸!ÈÐQUÊ²AÒ3VÑVÐX[ÐX\Ð]ˆˆ>ÑÜ�IÕùò 4Ws   Á	A(ÁA(c                 óR  — 	 t        t        | «      «      }t        «       |d<   t        «       5 }|j	                  dd| › �t        j                  |«      f«       |j                  «        ddd«       y# 1 sw Y   yxY w# t        $ r }t        j                  d|«       Y d}~yd}~ww xY w)zWDeja el estado en base para que cualquier worker lo pueda mostrar (solo uno es leader).Úpublished_atz7INSERT OR REPLACE INTO app_kv (key, value) VALUES (?,?)úorganizer_status:Nz/No se pudo publicar el estado del organizer: %s)Údictr8   r   r   r   ÚjsonÚdumpsÚcommitr   ÚloggerÚdebug)r   Úpayloadr   Úexcs       r    r?   r?   o   s•   € ðMÜ”{ 9Ó-Ó.ˆÜ",£,ˆ�ÑÜ‹Xð 	˜Ø�L‰LÐRØ-¨i¨[Ð9¼4¿:¹:ÀgÓ;NÐOôQà�K‰KŒM÷	÷ 	ñ 	ûô ò MÜ�‰ÐFÈ×LÑLûðMús4   ‚+A= ­;A1Á(A= Á1A:Á6A= Á:A= Á=	B&ÂB!Â!B&c                 óÂ  — 	 t        «       5 }|j                  dd| › �f«      j                  «       }d d d «       sy t        j                  |d   «      }t        «       t        j                  |d   «      z
  j                  «       }t        |j                  d«      «      xr  |t        dt        j                  dz  «      k  |d<   |S # 1 sw Y   Œ–xY w# t        $ r Y y w xY w)Nz$SELECT value FROM app_kv WHERE key=?rF   r   rE   r)   g     €f@é   )r   r   r   rH   Úloadsr   r   ÚfromisoformatÚtotal_secondsÚboolÚgetÚmaxr
   ÚORGANIZER_POLL_SECSr   )r   r   r   ÚdataÚages        r    Ú_read_publishedrZ   |   s×   € ðÜ‹Xð 	v˜Ø—,‘,ÐEÐJ[Ð\eÐ[fÐHgÐGiÓj×sÑsÓuˆC÷	váØÜ�z‰z˜#˜g™,Ó'ˆÜ‹xœ(×0Ñ0°°nÑ1EÓFÑF×UÑUÓWˆä˜TŸX™X gÓ.Ó/Òd°C¼#¸eÄV×E_ÑE_ÐbcÑEcÓ:dÑ4dˆˆW‰Øˆ÷	vð 	vûô ò Ùðús-   ‚
C Œ%C±
C ¼B	C ÃCÃC Ã	CÃCc                 óÂ  — t        | «      }|d   st        | «      }|r|}t        |«      }t        |j	                  dg «      «      |d<   t        t        | «      «      }||d<   	 t        t        | «      dz  «      |d<   |j	                  d«      xs |j	                  d«      }d	}|r/	 t        «       t        j                  |«      z
  j                  «       }|�t        |«      nd	|d
<   t        |j	                  d«       xs4 |dkD  xr- |j	                  d«       xr |d	uxr |t        j                   kD  «      |d<   |S # t        $ r d|d<   Y ŒÏw xY w# t        $ r d	}Y Œ‰w xY w)ua   Estado + diagnÃ³stico. `stalled` responde con datos a Â«Â¿hay fotos esperando que nadie mueve?Â».r)   r/   r%   Ú_unassignedÚunassigned_countr   r&   r*   NÚseconds_since_last_runr$   Ústalled)r8   rZ   rG   ÚlistrU   Ú_count_pendingr!   r   r   r   rR   rS   Ú
ValueErrorÚintrT   r
   ÚORGANIZER_STALL_SECS)r   r@   Ú	publishedÚoutÚpendingÚ	last_moverY   s          r    Úget_organizer_statusri   ‹   sq  € ä	�YÓ	€BØˆgŠ;Ü# IÓ.ˆ	ÙØˆBÜ
ˆr‹(€CÜ˜rŸv™v n°bÓ9Ó:€CˆÑäÔ-¨iÓ8Ó9€GØ"€CˆÑð$Ü"0Ô1AÀ)Ó1LÈ}Ñ1\Ó"]ˆÐÑð —‘�}Ó%Ò=¨¯©°Ó)=€IØ
€CÙð	Ü“8œh×4Ñ4°YÓ?Ñ?×NÑNÓPˆCð 14°¤C¨¤HÀT€CÐ Ñ!äØ�G‰G�GÓÐò 	qØ�a‰KÒo §¡¨Ó 5Ð5Òo¸#ÀT¸/ÒoÈcÔTZ×ToÑToÑNoó€Cˆ	�Nð €Jøô ò $Ø"#ˆÐÓð$ûô ò 	ØŠCð	ús$   Á#D< Â&.E Ä<EÅEÅEÅEc                 ót   — | €,t        t        t        «      d«      } | €i t        «       ¥dddœ¥S t	        | «      S )zBAlias para el monitor: sin colegio, el primero que haya arrancado.NF)r_   r^   )ÚnextÚiterr"   r3   ri   r7   s    r    Ú
get_statusrm   «   s@   € àÐÜœÔ/Ó0°$Ó7ˆ	ØÐØV”k“mÐV°ÐQUÒVÐVÜ 	Ó*Ð*r2   Úincomingc                 ób   — 	 t        d„ | j                  «       D «       «      S # t        $ r Y yw xY w)Nc              3   ó„   K  — | ]8  }|j                  «       sŒ|j                  j                  «       t        v sŒ5d –— Œ: y­w)r<   N)Úis_fileÚsuffixÚlowerÚ_IMAGE_EXTS_LOWER)Ú.0rB   s     r    ú	<genexpr>z!_count_pending.<locals>.<genexpr>¶   s+   è ø€ Òj˜°A·I±IµKÀAÇHÁHÇNÁNÓDTÔXiÒDi”1Ñjùs   ‚A ˜ A ¹A r   )ÚsumÚiterdirr   )rn   s    r    ra   ra   ´   s3   € ðÜÑj˜h×.Ñ.Ó0ÔjÓjÐjøÜò Ùðús   ‚" ¢	.­.c                 ó&  — | sg S i }| D ]   }	 |j                  «       j                  ||<   Œ" t        j                  t
        j                  «       g }| D ]Q  }	 |j                  «       j                  }||j                  |«      k7  s|dk(  r|j                  |j                  «       ŒS |r*t        j                  ddj                  |dd «      «       g S | S # t        $ r d||<   Y ŒÜw xY w# t        $ r |j                  |j                  «       Y Œ¼w xY w)uO   Difiere el LOTE ENTERO si algÃºn archivo sigue creciendo (subida FTP en curso).éÿÿÿÿr   u&   Archivos en transferencia (FTP): %sâ€¦ú, NrP   )ÚstatÚst_sizeÚOSErrorÚtimeÚsleepr
   ÚFTP_STABLE_SECSrU   ÚappendÚnamerK   ÚinfoÚjoin)r:   ÚsnapshotrB   Ú	uploadingÚsizes        r    Ú_filter_stabler‰   ½   s
  € áØˆ	Ø "€HØò ˆð	ØŸ&™&›(×*Ñ*ˆH�QŠKðô
 	‡J�JŒv×%Ñ%Ô&Ø€IØò %ˆð	%Ø—6‘6“8×#Ñ#ˆDØ�x—|‘| A“Ò&¨$°!ª)Ø× Ñ  §¡Ô(øð	%ñ Ü�‰Ð<¸d¿i¹iÈ	ÐRTÐSTÈÓ>VÔWØˆ	Ø€Løô ò 	ØˆH�Q‹Kð	ûô ò 	%Ø×Ñ˜QŸV™VÖ$ð	%ús$   �CÁAC)ÃC&Ã%C&Ã)$DÄDÚpathc                 ó”  — 	 ddl m} |j                  | «      5 }|j                  «       }d }|rVt	        |d«      r|j                  d«      ni }|j                  d«      xs$ |j                  d«      xs |j                  d«      }|rt        j                  |d«      cd d d «       S 	 d d d «       y # 1 sw Y   y xY w# t        $ r Y y w xY w)	Nr   )ÚImageÚget_ifdii‡  i�  i�  i2  z%Y:%m:%d %H:%M:%S)
ÚPILrŒ   ÚopenÚgetexifÚhasattrr�   rU   r   Ústrptimer   )rŠ   rŒ   ÚimgÚexifÚrawÚsubs         r    Ú_exif_datetimer—   Ö   sÃ   € ðÝØ�Z‰Z˜Óð 	C Ø—;‘;“=ˆDØˆCÙÜ.5°d¸IÔ.F�d—l‘l 6Ô*ÈB�Ø—g‘g˜e“nÒG¨¯©°«ÒG¸$¿(¹(À3»-�ÙÜ×(Ñ(¨Ð.AÓB÷	Cñ 	Cð ÷	Cð ÷	Cð ûô ò ØØðús5   ‚B; ™BB/Â	B; Â&B; Â/B8Â4B; Â8B; Â;	CÃCÚprefixeszset[str] | Nonec                 ó0  — 	 t        j                  t        | «      t         j                  «      }|€| dddfS | t	        j
                  || |«      t        | «      dfS # t        $ r1}t        j                  d| j                  |«       | dddfcY d}~S d}~ww xY w)uÁ   (ruta, texto QR | None, fecha EXIF | None, ilegible). ResoluciÃ³n completa: a 1/4 fallaban los QR
    que ocupan <17 % del encuadre y a 1/2 los <6 %; el volumen (lotes de 1-2 fotos) lo permite.NTFzError analizando %s: %s)Úcv2ÚimreadÚstrÚIMREAD_COLORr   Ú	detect_qrr—   r   rK   Úerrorrƒ   )rŠ   r˜   r“   rN   s       r    Ú_analyze_filer    æ   s�   € ð'Ü�j‰jœ˜T›¤C×$4Ñ$4Ó5ˆØˆ;Ø˜˜t TÐ)Ð)Ø”Y×(Ñ(¨¨d°HÓ=¼~ÈdÓ?SÐUZÐZÐZøÜò 'Ü�‰Ð.°·	±	¸3Ô?Ø�T˜4 Ð&Õ&ûð'ús"   ‚4A ·#A Á	BÁ$&BÂ
BÂBc                   ód  — e Zd Zdededefd„Zd)d„Zd*d	„Zd
eddde	e
ef   fd„Zd*d„Zde	eeef   fd„Zde
dedededdf
d„Zd+d
ededede	eef   fd„Zdedede	eef   fd„Zdede
de
de	eeef   fd„Zdededede
de	eeef   f
d„Zd ed!ee   d"edefd#„Zed$edefd%„«       Zd&ed'edefd(„Zy),ÚPhotoOrganizerr   Úincoming_dirÚ
output_dirc                 ó  — || _         || _        || _        | j                  dz  | _        | j                  j	                  dd¬«       | j                  j	                  dd¬«       t        j                  «       | _        d | _        d| _	        y )NÚ_errorsT©ÚparentsÚexist_okç        )
r   rn   ÚoutputÚ	error_dirÚmkdirÚasyncioÚLockÚlockÚ	_prefixesÚ_prefixes_at)Úselfr   r£   r¤   s       r    Ú__init__zPhotoOrganizer.__init__ö   sq   € Ø"ˆŒØ$ˆŒØ ˆŒØŸ™¨Ñ2ˆŒØ�‰×Ñ T°DÐÔ9Ø�‰×Ñ $°ÐÔ6Ü—L‘L“NˆŒ	Ø,0ˆŒØˆÕr2   r   úset[str]c           	      óD  — t        j                  «       }| j                  �|| j                  z
  t        kD  r¨d }	 t        j                  | j                  «      j                  d«      xs d }t        j                  || j                  «      | _        || _        t        j                  d| j                  dj!                  t#        | j                  «      «      xs d«       | j                  S # t        $ r+}t        j                  d| j                  |«       Y d }~Œ®d }~ww xY w)NÚqr_code_prefixz'[%s] No se pudo leer qr_code_prefix: %sz$[%s] Prefijos del OCR de rescate: %sr{   z	(ninguno))r   Ú	monotonicr±   r²   Ú_PREFIX_TTLr   Úget_settingsr   rU   r   rK   Úwarningr   Úvalid_prefixesr«   r„   r…   Úsorted)r³   ÚnowÚ
configuredrN   s       r    Ú_code_prefixeszPhotoOrganizer._code_prefixes  só   € Ü�n‰nÓˆØ�>‰>Ð! S¨4×+<Ñ+<Ñ%<¼{Ò%JØˆJð_Ü+×8Ñ8¸¿¹ÓH×LÑLÐM]Ó^ÒfÐbf�
ô '×5Ñ5°jÀ$Ç+Á+ÓNˆDŒNØ #ˆDÔÜ�K‰KÐ>ÀÇÁÐPT×PYÑPYÔZ`Ðae×aoÑaoÓZpÓPqò  QAð  vAô  BØ�~‰~Ðøô ò _Ü—‘ÐHÈ$Ï.É.ÐZ]×^Ñ^ûð_ús   º2C+ Ã+	DÃ4!DÄDNc                 ó  — t        j                   «       }	 t        | j                  j                  «       «      }|D ]Ã  }	 |j                  j                  «       }|j                  «       r+|t        v s#|t        v s|j                  j                  d«      rŒZ||j                  «       j                   z
  t"        j$                  k  rŒ‰|j'                  «        t
        j)                  d| j                  |j                  «       ŒÅ y# t        $ r+}t
        j                  d| j                  |«       Y d}~yd}~ww xY w# t*        $ r Y �Œ
t        $ r7}t
        j                  d| j                  |j                  |«       Y d}~�ŒDd}~ww xY w)z¹Quita de incoming lo que no se puede procesar (RAW .NEF, restos de FTP): si no, se acumula para
        siempre. Conserva .dat y solo toca ficheros 'asentados' (no una subida en curso).z/[%s] No se pudo leer incoming para limpieza: %sNú.z-[%s] No procesable, eliminado de incoming: %sz[%s] No se pudo eliminar %s: %s)r   r`   rn   rx   r   rK   rŸ   r   rr   rs   rq   rt   Ú_PURGE_KEEP_EXTSrƒ   Ú
startswithr|   Úst_mtimer
   ÚPURGE_MIN_AGE_SECSÚunlinkr„   ÚFileNotFoundError)r³   r¾   ÚentriesrN   rB   Úexts         r    Ú_purge_unprocessablez#PhotoOrganizer._purge_unprocessable  s7  € ô �i‰i‹kˆð	Ü˜4Ÿ=™=×0Ñ0Ó2Ó3ˆGð ò 	]ˆAð]Ø—h‘h—n‘nÓ&�Ø—y‘y”{ cÔ->Ñ&>À#ÔIYÑBYÐ]^×]cÑ]c×]nÑ]nÐorÔ]sØØ˜Ÿ™›×*Ñ*Ñ*¬V×-FÑ-FÒFØØ—‘”
Ü—‘ÐKÈTÏ^É^Ð]^×]cÑ]cÕdñ	]øô ò 	Ü�L‰LÐJÈDÏNÉNÐ\_Ô`Üûð	ûô %ò ÚÜò ]Ü—‘Ð>ÀÇÁÐPQ×PVÑPVÐX[×\Ò\ûð]úsA   –#D ¿AD9Â.D9Ã;D9Ä	D6Ä!D1Ä1D6Ä9	FÅFÅ,E?Å?FÚ
candidatesr˜   c           
      óÜ  — i }g }t        t        ¬«      5 }|D �ci c]  }|j                  t        ||«      |“Œ }}t	        |«      D ]%  }||   }		 |j                  «       \  }
}}}|||f||
<   Œ' 	 ddd«       ||fS c c}w # t        $ rS}t        j                  d| j                  |	j                  |d¬«       |j                  |	j                  «       Y d}~Œ‘d}~ww xY w# 1 sw Y   ||fS xY w)u_   AnÃ¡lisis en paralelo (en un hilo). Un archivo que revienta se queda fuera sin tumbar al resto.)Úmax_workersz[%s] Fallo analizando %s: %sT©Úexc_infoN)r   Ú_WORKERSÚsubmitr    r   Úresultr   rK   rŸ   r   rƒ   r‚   )r³   rÌ   r˜   ÚcacheÚfailedÚpoolrB   ÚfuturesÚfutÚsrcrŠ   ÚqrÚdtÚ
unreadablerN   s                  r    Ú_analyze_batchzPhotoOrganizer._analyze_batch&  sø   € à#%ˆØˆÜ¬HÔ5ð 		,¸ØKUÖVÀa�t—{‘{¤=°!°XÓ>ÀÑAÐVˆGÐVÜ# GÓ,ò ,�Ø˜c‘l�ð,Ø/2¯z©z«|Ñ,�D˜"˜b *Ø#% r¨:Ð"6�E˜$’Kñ	,÷		,ð �fˆ}Ðùò Wøô !ò ,Ü—L‘LÐ!?ÀÇÁÐQT×QYÑQYÐ[^Ðim�LÔnØ—M‘M #§(¡(×+Ñ+ûð,ú÷		,ð �fˆ}ÐúsF   •CšA;¹CÁB Á,CÁ;CÂ 	CÂ	A	CÃCÃCÃCÃC+c           
   ƒ   ó"  ‡K  — t        | j                  «      }|d   ryd|d<   t        j                  «       }g }	 	 | j                  j                  dd¬«       t        j                  | j                  «      ƒ d{  –—†  	 | j                  j                  «       D �cg c]O  }|j                  «       r=|j                  j                  «       t         v r|j"                  j%                  d«      s|‘ŒQ }}t/        |«      |d
<   |sKt1        «       |d<   d|d<   d|d<   	 d	|d<   t+        | j                  «      |d
<   t-        | j                  «       yt        j3                  d| j                  t/        |«      | j                  «       t        j                  t4        |«      ƒ d{  –—† }|s4	 d	|d<   t+        | j                  «      |d
<   t-        | j                  «       yt        j                  | j6                  «      ƒ d{  –—† \  }}}	|rd| j9                  |«      ƒ d{  –—† \  }
}| j;                  |||
|«       	 d	|d<   t+        | j                  «      |d
<   t-        | j                  «       yt        j                  | j<                  «      ƒ d{  –—† }t        j                  | j>                  ||«      ƒ d{  –—† \  Š}||z  }|D �cg c]	  }|‰v sŒ|‘Œ }}dt@        fˆfd„}|jC                  |¬«       d}
|D ]U  }‰jE                  |d«      \  }}}	 | jG                  |||«      ƒ d{  –—† r|
dz  }
n|jI                  |j"                  «       ŒW t1        «       |d<   |
|d<   tK        t        j                  «       |z
  dz  «      |d<   |r9t)        | j                  t/        |«      › ddjM                  |dd «      › �|¬«       nt1        «       |d<   d|d<   d|d<   g |d<   d	|d<   t+        | j                  «      |d
<   t-        | j                  «       y# t        $ r7}t        j                  d| j                  | j                  |«       Y d}~�Œöd}~ww xY w7 �ŒÜc c}w # t&        $ rv}t        j                  d| j                  |«       t)        | j                  d|› �«       Y d}~d	|d<   t+        | j                  «      |d
<   t-        | j                  «       yd}~ww xY w7 �ŒA7 �Œç7 �ŒË7 �Œ\7 �Œ6c c}w 7 �ŒË# t&        $ rT}t        j                  d| j                  |j"                  |d¬«       |jI                  |j"                  «       Y d}~�ŒVd}~ww xY w# d	|d<   t+        | j                  «      |d
<   t-        | j                  «       w xY w­w) u_   Organiza lo que haya en incoming. Todo el cuerpo va en try/finally (ver docstring del mÃ³dulo).r$   NTr§   z[%s] Could not recreate %s: %srÂ   z![%s] No se pudo leer incoming: %szNo se pudo leer incoming: Fr%   r+   r   r.   r,   z![%s] Detectados %d archivos en %srB   c                 ó¶   •— ‰|    d   }|r|S 	 t        j                  | j                  «       j                  «      S # t        $ r t         j
                  cY S w xY w)Nr<   )r   Úfromtimestampr|   rÅ   r~   rV   )rB   rÛ   rÔ   s     €r    Ú	_sort_keyz1PhotoOrganizer.process_pending.<locals>._sort_keye  sS   ø€ Ø˜1‘X˜a‘[�ÙØ�Ið(Ü#×1Ñ1°!·&±&³(×2CÑ2CÓDÐDøÜò (Ü#Ÿ<™<Ò'ð(ús   �,< ¼AÁA)Úkey)NNFr<   z[%s] Fallo organizando %s: %srÏ   r&   r'   éè  r(   ú& archivo(s) no se pudieron organizar: r{   rP   ©r:   r/   )'r8   r   r   r¸   rn   r­   r~   rK   rŸ   r®   Ú	to_threadrË   rx   rq   rr   rs   rt   rƒ   rÄ   r   rC   ra   r?   Úlenr   r„   r‰   Ú_shoot_settingsÚ_process_shootÚ_finish_passrÀ   rÝ   r   ÚsortrU   Ú_process_oner‚   rc   r…   )r³   r@   Út0rÕ   rN   rB   rÌ   Úshoot_onÚgapÚ	max_childÚmovedr˜   Úanalysis_failedr:   rá   rÚ   Ú_dtrÜ   rÔ   s                     @r    Úprocess_pendingzPhotoOrganizer.process_pending6  s!  øè ø€ ä˜Ÿ™Ó(ˆØˆlÒØØˆˆ<ÑÜ�^‰^ÓˆØˆðM	,ðcØ—‘×#Ñ#¨D¸4Ð#Ô@ô ×#Ñ# D×$=Ñ$=Ó>×>Ð>ðØ)-¯©×)>Ñ)>Ó)@ö w AØ!"§¡¤°·±·±Ó1AÔEVÑ1VÐ_`×_eÑ_e×_pÑ_pÐqtÔ_uò  ð w�
ð wô #& j£/ˆBˆÑÙÜ#-£<��<Ñ Ø+,�Ð'Ñ(Ø#'��<Ñ Øðl  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+ôm �K‰KÐ;¸T¿^¹^ÌSÐQ[Ë_Ð^b×^kÑ^kÔlÜ&×0Ñ0´ÀÓL×LˆJÙØðb  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+ôc .5×->Ñ->¸t×?SÑ?SÓ-T×'TÑ$ˆH�c˜9ÙØ&*×&9Ñ&9¸*Ó&E× E‘��vØ×!Ñ! " b¨%°Ô8ØðV  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+ôW %×.Ñ.¨t×/BÑ/BÓC×CˆHÜ+2×+<Ñ+<¸T×=PÑ=PÐR\Ð^fÓ+g×%gÑ"ˆE�?Ø�oÑ%ˆFØ *Ö9˜1¨a°5ªj’QÐ9ˆEÐ9ð(œTõ (ð �J‰J˜9ˆJÔ%àˆEØò 	*�Ø&+§i¡i°Ð3FÓ&GÑ#��C˜ð*Ø!×.Ñ.¨q°"°jÓA×AÐAØ ™
™àŸ™ a§f¡fÔ-øð	*ô !+£ˆBˆ}ÑØ).ˆBÐ%Ñ&Ü$'¬¯©Ó)9¸BÑ)>À$Ñ(FÓ$GˆBÐ Ñ!ÙÜ˜dŸn™nÜ!$ V£ Ð-SÐTX×T]ÑT]Ð^dÐegÐfgÐ^hÓTiÐSjÐkÐsyö{ô $.£<��<Ñ Ø+,�Ð'Ñ(Ø#'��<Ñ Ø%'��>Ñ"ð  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+øôQ ò cÜ—‘Ð=¸t¿~¹~ÈtÏ}É}Ð^a×bÒbûðcúà>ûòwøäò Ü—‘Ð@À$Ç.Á.ÐRUÔVÜ˜dŸn™nÐ0JÈ3È%Ð.PÔQÛðz  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+ûðEúð Mùð (Uùà Eùð DùØ%gûâ9ð" Búô !ò *Ü—L‘LÐ!@À$Ç.Á.ÐRS×RXÑRXÐZ]Ðhl�LÔmØ—M‘M !§&¡&×)Ò)ûð*ûð"  %ˆBˆ|ÑÜ"0°·±Ó"?ˆBˆÑÜ˜DŸN™NÕ+üs^  ƒ7V¼P Á"U Á;QÁ<U ÂQ ÂAQÃ1Q Ã3'U Ä3VÅAU Æ S!Æ!U Æ(3VÇ"U Ç=S$Ç>U ÈS'ÈU È93VÉ,"U ÊS*Ê(U Ê7S-Ê8U Ë	S0ËS0Ë=U ÌS8Ì*S5Ì+%S8ÍBU Ï!3VÐ	QÐ,QÑ	U ÑQÑU ÑQ Ñ	SÑ(:SÒ"U Ò&3VÓSÓU Ó$U Ó'U Ó*U Ó-U Ó0U Ó5S8Ó8	UÔA	UÕ
U ÕUÕU Õ4VÖVc                 ó  — 	 t        j                  | j                  «      }|j                  d«      dk(  dt	        |j                  d«      xs d«      fS # t
        $ r+}t        j                  d| j                  |«       Y d }~yd }~ww xY w)NÚ
shoot_modeÚtruerª   Úshoot_max_per_childrP   zD[%s] Could not read shoot settings, using the card-in-photo mode: %s)Frª   rP   )r   rº   r   rU   rc   r   rK   r»   )r³   ÚsrN   s      r    rè   zPhotoOrganizer._shoot_settings�  sw   € ð	!Ü×+Ñ+¨D¯N©NÓ;ˆAØ—E‘E˜,Ó'¨6Ñ1°3¼¸A¿E¹EÐBWÓ<XÒ<]Ð\]Ó8^Ð_Ð_øÜò 	!Ü�N‰NÐaÐcg×cqÑcqÐsvÔwÜ ûð	!ús   ‚AA Á	BÁ!BÂBr@   rí   rñ   rÕ   c           
      ó"  — t        «       |d<   ||d<   t        t        j                  «       |z
  dz  «      |d<   |r9t	        | j
                  t        |«      › ddj                  |d d «      › �|¬«       y t        «       |d	<   d
|d<   d |d<   g |d<   y )Nr&   r'   rã   r(   rä   r{   rP   rå   r+   r   r.   r,   r/   )r   rc   r   r¸   rC   r   rç   r…   )r³   r@   rí   rñ   rÕ   s        r    rê   zPhotoOrganizer._finish_pass—  s¦   € Ü&›Lˆˆ=ÑØ%*ˆÐ!Ñ"Ü #¤T§^¡^Ó%5¸Ñ%:¸dÑ$BÓ CˆÐÑÙÜ˜$Ÿ.™.Ü  ›[˜MÐ)OÐPT×PYÑPYÐZ`ÐacÐbcÐZdÓPeÐOfÐgÐouöwô  *›|ˆBˆ|ÑØ'(ˆBÐ#Ñ$Ø#ˆBˆ|ÑØ!#ˆBˆ~Òr2   Ú_gapÚ
_max_childc           
   ƒ   óf  K  — dg }}i }|D ]±  }t        j                  t        j                  | j                  |j
                  «      ƒ d {  –—† }|sK| j                  || j                  |j
                  z  «      r|dz  }n|j                  |j
                  «       Œ‘|j                  |g «      j                  |«       Œ³ |j                  «       D ]a  \  }}	| j                  4 ƒd {  –—†  	 t        j                  | j                  ||	«      ƒ d {  –—† \  }
}||
z  }||z  }d d d «      ƒd {  –—†  Œc ||fS 7 Œì7 ŒS7 Œ+# t        $ rQ}t        j                  d| j                  ||d¬«       ||	D �cg c]  }|j
                  ‘Œ nc c}w c}z  }Y d }~Œqd }~ww xY w7 Œm# 1 ƒd {  –—†7  sw Y   ŒßxY w­w)Nr   r<   z![%s] Shoot pass failed for %s: %sTrÏ   )r®   ræ   r   Úresolve_photographerr   rƒ   Ú_mover¬   r‚   r6   Úitemsr°   Ú_shoot_photographerr   rK   rŸ   )r³   rÌ   rû   rü   rñ   rÕ   Úby_photographerrB   Úphr:   ÚmÚflrN   s                r    ré   zPhotoOrganizer._process_shoot¤  s˜  è ø€ Ø˜2ˆvˆØ+-ˆØò 	9ˆAÜ×(Ñ(Ô)=×)RÑ)RÐTX×TbÑTbÐde×djÑdjÓk×kˆBÙØ—:‘:˜a §¡°!·&±&Ñ!8Ô9Ø˜Q‘J‘Eà—M‘M !§&¡&Ô)ØØ×&Ñ& r¨2Ó.×5Ñ5°aÕ8ð	9ð )×.Ñ.Ó0ò 	6‰IˆB�Ø—y‘y÷ 6ñ 6ð6Ü")×"3Ñ"3°D×4LÑ4LÈbÐRWÓ"X×X‘E�A�rØ˜Q‘J�EØ˜b‘L�F÷	6÷ 6ð 6ð	6ð �fˆ}Ðð# løð6øàXùô !ò 6Ü—L‘LÐ!DÀdÇnÁnÐVXÐZ]Ðhl�LÔmØ¨uÖ5¨!˜qŸv›vÑ5ùÔ5Ñ5•Fûð6úð6ø÷ 6÷ 6ñ 6üs�   ‚AF1Á
D7ÁBF1Ã%D9Ã&F1Ã)FÃ+$D=ÄD;ÄD=Ä!F1Ä,FÄ-F1Ä9F1Ä;D=Ä=	FÅ)FÅ/FÆFÆFÆFÆFÆF1ÆF.	Æ"F%Æ#F.	Æ*F1r  r:   c                 ó*  — | j                   }t        j                  t        j                  ||«      «      }t        j                  ||«      }|D �ci c]  }|j
                  |“Œ }}dg }	}|D ]p  }
|
j                  r|
j                  |d   k  rŒ"| j                  ||
||«      \  }}}||z  }|	|z  }	|dk(  r n(|
j                  |d<   t        j                  |||«       Œr t        j                  ||d   «      }|r|d   j                  s|d   nd}d}t        j                  «       }|D ]®  }|j
                  |v s|j                  «       sŒ"|�"|j
                  t        |j                  «      vrŒF	 ||j                  «       j                   z
  }|t$        k\  sŒn| j'                  || j)                  | j*                  dz  |j
                  z  «      «      sŒª|dz  }Œ° |r"ddlm} |j1                  d	|› �d
|› d|› d�«       ||	fS c c}w # t"        $ r Y Œéw xY w)z\Turn this photographer's closed classes into children, in order. Blocking: runs in a thread.r   Ú	processedÚwaitrz   Nr\   r<   ©Úalert_servicezshoot-unassigned:z!Photos taken with no class activez photo(s) from z’ were taken while no class was open on the phone and were parked in incoming/_unassigned/. Nobody can see them; scan the class QR before shooting.)r   r   Ú
build_runsÚ
events_forÚ	get_staterƒ   ÚclosedÚidÚ_materialize_runÚ	set_stateÚclaimed_namesr   Úexistsr>   Úbeforer|   rÅ   r~   Ú_UNASSIGNED_GRACE_SECSrÿ   Ú_uniquern   Úservicesr
  Únotify)r³   r  r:   ÚschoolÚrunsÚstaterB   Úby_namerñ   rÕ   ÚrunÚoutcomer  r  ÚclaimedÚopenedÚparkedr¾   rY   r
  s                       r    r  z"PhotoOrganizer._shoot_photographer»  s  € à—‘ˆÜ×'Ñ'¬×(@Ñ(@ÀÈÓ(LÓMˆÜ×'Ñ'¨°Ó3ˆØ&+Ö, �1—6‘6˜1‘9Ð,ˆÐ,Ø˜2ˆvˆàò 		7ˆCØ—:’: §¡¨5°Ñ+=Ò!=ØØ!×2Ñ2°2°s¸GÀUÓK‰NˆG�Q˜Ø�Q‰JˆEØ�b‰LˆFØ˜&Ò ÙØ!$§¡ˆE�+ÑÜ×#Ñ# F¨B°Õ6ð		7ô  ×-Ñ-¨d°E¸+Ñ4FÓGˆÙ!¨$¨r©(¯/ª/��b’¸tˆØˆÜ�i‰i‹kˆØò 
	ˆAØ�v‰v˜Ñ ¨¯©¬
ØØÐ! a§f¡f´C¸¿¹Ó4FÑ&FØðØ˜AŸF™F›H×-Ñ-Ñ-�ð Ô,Ó,°·±¸A¸t¿|¹|ÈDÏMÉMÐ\iÑLiÐlm×lrÑlrÑLrÓ?sÕ1tØ˜!‘‘ð
	ñ Ý.Ø× Ñ Ø# F 8Ð,Ð.QØ�(˜/¨"¨ð .að bôcð �fˆ}ÐùòK -øô6 ò Ùðús   ÁHÅ/HÈ	HÈHr  r  c                 óÜ  ‡ — ‰ j                   }dg }}|j                  }|€‰ j                  ||g d|«      S t        j                  |||j
                  «      }	|	�€ |D �
cg c]	  }
|
|vsŒ|
‘Œ }}
|r:t        ˆ fd„|D «       «      rddg fS ‰ j                  |||t        |«      › d�|«      S |j                  t        |«      k7  r/‰ j                  |||d|j                  › dt        |«      › d	�|«      S t        j                  |D �
cg c]  }
||
   ‘Œ	 c}
d
„ «      }|D �cg c]  }|j                  ‘Œ c}ddg dœ}	t        j                  |||j
                  |	«       |	d   }t        |	d   t        |j                  «      «      D �]Ö  }|j                  |   \  }}
||	d   |	d   |
z    }d}|t        j                  k(  r¿|	j                  di «      j!                  t#        |«      «      }|sSt        j$                  ||j&                  «      }||	d   t#        |«      <   t        j                  |||j
                  |	«       ‰ j(                  |z  |z  }t*        j-                  d|||j&                  |t        |«      «       n‰ j.                  dz  }d}|D ]_  }‰ j.                  |z  }|j1                  «       sŒ#‰ j3                  |‰ j5                  ||z  «      «      r|dz  }ŒM|j7                  |«       d}Œa |sd||fc S |dz   |	d   |
z   c|	d<   |	d<   |r|	d   j7                  |«       t        j                  |||j
                  |	«       t9        j:                  |«       �ŒÙ d||fS c c}
w c c}
w c c}w )z½("done" | "wait", moved, failed). A run is turned into children only if what was declared on the
        phone equals the files of its fence; otherwise nothing is assigned (see _block_run).r   Nz+the class was never closed with a file listc              3   óX   •K  — | ]!  }‰j                   |z  j                  «       –— Œ# y ­wr5   )rn   r  )ru   Únr³   s     €r    rv   z2PhotoOrganizer._materialize_run.<locals>.<genexpr>ô  s#   øè ø€ ÒE¸˜Ÿ™¨Ñ)×1Ñ1×3ÑEùs   ƒ'*r  z file(s) of the class are gonez	declared z photo(s) but z arrivedc                 óN   — t        | «      rt        | «      j                  «       S d S r5   )r—   Ú	timestamp)Úps    r    ú<lambda>z1PhotoOrganizer._materialize_run.<locals>.<lambda>û  s"   € ÔYgÐhiÔYj¼ÀqÓ8I×8SÑ8SÓ8U€ Ðpt€ r2   )ÚorderÚiÚposÚcodesr)  r*  r+  rg   z[%s] %s: %s -> %s (%d photo(s))Ú_ignoredTr<   Fr,  Údone)r   r:   Ú
_block_runr   Úget_progressr  Úanyrç   ÚdeclaredÚsort_capture_orderrƒ   Úset_progressÚranger   Ú
KIND_CHILDr6   rU   rœ   Úcreate_child_familyÚ
class_namer«   rK   r„   rn   r  rÿ   r  r‚   r   Úinvalidate_families_cache)r³   r  r  r  r  r  rñ   rÕ   ÚnamesÚprogressr$  ÚmissingÚorderedr'  r)  ÚidxÚkindÚgroupÚcodeÚdest_dirÚokrƒ   rÙ   s   `                      r    r  zPhotoOrganizer._materialize_runç  s]  ø€ ð —‘ˆØ˜2ˆvˆØ—	‘	ˆØˆ=Ø—?‘? 2 s¨BÐ0]Ð_dÓeÐeä ×-Ñ-¨f°b¸#¿&¹&ÓAˆØÑØ"'Ö<˜Q¨1°GÒ+;’qÐ<ˆGÐ<ÙÜÓE¸WÔEÔEØ! 1 b˜=Ð(Ø—‘ r¨3°¼#¸g»,¸ÐGeÐ7fÐhmÓnÐnØ�|‰|œs 5›zÒ)Ø—‘Ø˜˜U i°·±¨~¸^ÌCÐPUËJÈ<ÐW_Ð$`Ðbgóið iä#×6Ñ6Ø%*Ö+ �˜“Ò+Ñ-uówˆGà29Ö!:¨Q !§&£&Ò!:ÀÈ1ÐWYÑZˆHÜ×&Ñ& v¨r°3·6±6¸8ÔDà˜Ñ!ˆÜ˜ #™¬¨C¯I©I«Ó7ó 	<ˆCØ—i‘i ‘n‰GˆD�!Ø˜( 5™/¨(°5©/¸AÑ*=Ð>ˆEØˆDØ”}×/Ñ/Ò/à×*Ñ*¨9°bÓ9×=Ñ=¼cÀ#»hÓG�ÙÜ(×<Ñ<¸VÀSÇ^Á^ÓT�DØ48�H˜YÑ'¬¨C«Ñ1Ü!×.Ñ.¨v°r¸3¿6¹6À8ÔLØŸ;™;¨Ñ+¨dÑ2�Ü—‘Ð=¸vÀrÈ3Ï>É>Ð[_ÔadÐejÓakÕlàŸ=™=¨:Ñ5�ØˆBØò �Ø—m‘m dÑ*�Ø—z‘z”|ØØ—:‘:˜c 4§<¡<°¸4±Ó#@ÔAØ˜Q‘J‘Eà—M‘M $Ô'Ø‘Bðñ Ø˜u fÐ,Ò,Ø-0°1©W°h¸u±oÈÑ6IÐ*ˆH�S‰M˜8 E™?ÙØ˜Ñ!×(Ñ(¨Ô.Ü×&Ñ& v¨r°3·6±6¸8ÔDÜ×3Ñ3°FÖ;ð?	<ð@ �u˜fÐ$Ð$ùò] =ùò ,ùÚ!:s   Á	MÁ'MÄM$ÄM)r:  Úreasonc                 óÜ  — | j                   }dg }}|D ]z  }	| j                  |	z  }
|
j                  «       sŒ#| j                  |
| j	                  | j                  dz  d|j
                  › �z  |	z  «      «      r|dz  }Œj|j                  |	«       Œ| |rd||fS |j                  dg «      j                  |j
                  |j                  |j                  t        |«      |dœ«       |d   dd	 |d<   dd
lm} |j                  d|› d|j
                  › �d|j                  › d�|› d|› dt        |«      › d|j
                  › d�«       t        j                  d|||j                  |«       d||fS )uÌ   The phone and the folder disagree: assign NOTHING of this class. Its files are parked (not lost) and
        the operator is told. Only this class is affected â€” the fence keeps the next one independent.r   r\   zclass-r<   r  Úblocked)r  Úclassr2  ÚreceivedrD  r=   Nr	  zshoot-blocked:ú:zClass z was not assignedz: z. Its z4 photo(s) were parked in incoming/_unassigned/class-z0/ and no family code was created for this class.u    [%s] %s: class %s blocked â€” %sr.  )r   rn   r  rÿ   r  r  r‚   r6   r8  r2  rç   r  r
  r  rK   rŸ   )r³   r  r  r:  rD  r  r  rñ   rÕ   r$  rÙ   r
  s               r    r/  zPhotoOrganizer._block_run"  sƒ  € ð —‘ˆØ˜2ˆvˆØò 	%ˆAØ—-‘- !Ñ#ˆCØ�z‰z�|Ø—:‘:˜c 4§<¡<°·±ÀÑ0MÐRXÐY\×Y_ÑY_ÐX`ÐPaÑ0aÐdeÑ0eÓ#fÔgØ˜Q‘J‘Eà—M‘M !Õ$ð	%ñ Ø˜5 &Ð(Ð(Ø×Ñ˜ BÓ'×.Ñ.Ø—6‘6 C§N¡NÀÇÁÔZ]Ð^cÓZdÐpvñ0xô 	yà  Ñ+¨C¨DÐ1ˆˆiÑÝ*Ø×ÑØ˜V˜H A c§f¡f XÐ.°&¸¿¹Ð8HÐHYÐ0ZØˆd�"�V�H˜F¤3 u£: ,Ð.bÐcf×ciÑciÐbjð k9ð :ô	;ô 	�‰Ð7¸ÀÀSÇ^Á^ÐU[Ô\Ø�u˜fÐ$Ð$r2   Ú	file_pathÚqr_detectedrÜ   c              ƒ   óº  K  — |r*| j                  || j                  |j                  z  «       yt        j                  t
        j                  | j                  |j                  «      ƒ d{  –—† }|s*| j                  || j                  |j                  z  «       y| j                  4 ƒd{  –—†  |rt        j                  |«      nd}|r1|s/t        j                  d| j                  |dd |j                  «       |rV|}t        j                  t
        j                  | j                  ||«      ƒ d{  –—†  |t        | j                  «      d   |<   n7t        j                  t
        j                  | j                  |«      ƒ d{  –—† }| j                   |z  |z  }|j#                  dd¬«       |rc|dz  }|j#                  dd¬«       | j%                  ||j                  z  «      }	t        j'                  d| j                  ||j                  «       n| j%                  ||j                  z  «      }	| j                  ||	«      s	 ddd«      ƒd{  –—†  y	t)        j*                  | j                  «       t        j'                  d
| j                  |j                  ||«       	 ddd«      ƒd{  –—†  y7 �ŒK7 �Œ7 �Œˆ7 �Œ87 Œo7 Œ# 1 ƒd{  –—†7  sw Y   yxY w­w)u`   Coloca una foto. False si se quedÃ³ donde estaba (`_move` no lanza: hay que mirar su resultado).TNu/   [%s] QR sin cÃ³digo vÃ¡lido ignorado (%r) en %sé<   r0   r§   z.qrz[%s] Tarjeta detectada: %s (%s)Fu   [%s] %s â†’ %s/%s)rÿ   r¬   rƒ   r®   ræ   r   rþ   r   r°   r   Úextract_coderK   r»   Úset_sticky_qrr8   Úget_sticky_qrr«   r­   r  r„   r   r9  )
r³   rJ  rK  rÜ   ÚphotographerÚcardrA  rB  Úqr_subÚdests
             r    rì   zPhotoOrganizer._process_one;  sh  è ø€ áØ�J‰J�y $§.¡.°9·>±>Ñ"AÔBØä$×.Ñ.Ô/C×/XÑ/XÐZ^×ZhÑZhÐjs×jxÑjxÓy×yˆÙØ�J‰J�y $§.¡.°9·>±>Ñ"AÔBØà—9‘9÷ 	ñ 	Ù:E”9×)Ñ)¨+Ô6È4ˆDÙ¡4Ü—‘ÐPÐRV×R`ÑR`ÐbmÐnqÐoqÐbrÐt}÷  uCñ  uCô  DÙØ�Ü×'Ñ'Ô(<×(JÑ(JÈDÏNÉNÐ\hÐjnÓo×oÐoØVZ”˜DŸN™NÓ+Ð,DÑEÀlÒSä$×.Ñ.Ô/C×/QÑ/QÐSW×SaÑSaÐcoÓp×p�à—{‘{ \Ñ1°DÑ8ˆHØ�N‰N 4°$ˆNÔ7ÙØ! EÑ)�Ø—‘ T°D�Ô9Ø—|‘| F¨Y¯^©^Ñ$;Ó<�Ü—‘Ð=¸t¿~¹~ÈtÐU^×UcÑUcÕdà—|‘| H¨y¯~©~Ñ$=Ó>�Ø—:‘:˜i¨Ô.Ø÷+	÷ 	ð 	ô, ×3Ñ3°D·N±NÔCÜ�K‰KÐ+¨T¯^©^¸Y¿^¹^È\Ð[_Ô`Ø÷1	÷ 	ð 	ð zùð
	ùð pùð qùð	øð 	ø÷ 	÷ 	ñ 	üs¡   ‚A)KÁ+J6Á,A KÂ,J9Â-KÂ0BKÄ4J<Ä5AKÆJ?ÆB?KÉKÉKÉKÉAKÊ%KÊ0KÊ1KÊ9KÊ<KÊ?KËKËKËKËKËKËKrŠ   c                 ó®   — | d}}|j                  «       r@| j                  | j                  › d|› | j                  › �z  }|dz  }|j                  «       rŒ@|S )Nr<   Ú_)r  ÚparentÚstemrr   )rŠ   r'  r$  s      r    r  zPhotoOrganizer._unique`  sT   € à�Qˆ1ˆØ�h‰hŒjØ—‘ §¡ ¨1¨Q¨C°·±¨}Ð=Ñ=ˆAØ�‰FˆAð �h‰h�jð ˆr2   rÙ   Údstc                 ó”  — 	 |j                   j                  dd¬«       t        j                  t	        |«      t	        |«      «       y# t
        $ r9 t        j                  d| j                  |j                  |j                   «       Y yt        $ r6}t        j                  d| j                  |j                  |«       Y d }~yd }~ww xY w)NTr§   z/[%s] Error de PERMISOS al mover %s (carpeta %s)z[%s] Error moviendo %s: %sF)rW  r­   ÚshutilÚmoverœ   ÚPermissionErrorrK   rŸ   r   rƒ   r   )r³   rÙ   rY  rN   s       r    rÿ   zPhotoOrganizer._moveh  s¡   € ð	VØ�J‰J×Ñ T°DÐÔ9Ü�K‰Kœ˜C›¤# c£(Ô+ØøÜò 	rÜ�L‰LÐJÈDÏNÉNÐ\_×\dÑ\dÐfi×fpÑfpÕqð ô ò 	VÜ�L‰LÐ5°t·~±~ÀsÇxÁxÐQT×UÐUØûð	Vús   ‚AA Á?CÂ	CÂ,CÃC)r   rµ   ©r   N)rª   r   )Ú__name__Ú
__module__Ú__qualname__rœ   r   r´   rÀ   rË   r`   ÚtuplerG   rÝ   rô   rT   Úfloatrc   rè   rê   ré   r  r  r/  r   rì   Ústaticmethodr  rÿ   r1   r2   r    r¢   r¢   õ   s²  „ ð	  #ð 	 °Tð 	 Àtó 	 óó]ð.¨ð ¸ð ÈÈdÐTXÈjÑHYó ó U,ðr!  t¨U°CÐ'7Ñ!8ó !ð$˜tð $¨ð $°sð $ÀDð $ÈTó $ñ¨tð ¸5ð ÐTWð Ð`eÐfiÐkoÐfoÑ`pó ð.* cð *°$ð *¸5ÀÀdÀÑ;Kó *ðX9% 3ð 9%°dð 9%À4ð 9%ÈEÐRUÐWZÐ\`ÐR`ÑLaó 9%ðv%˜Sð %¨dð %¸Cð %Èð %ÐQVÐWZÐ\_ÐaeÐWeÑQfó %ð2#¨Dð #¸xÈ¹}ð #ÐZ^ð #Ðcgó #ðJ ð�dð ˜tò ó ðð	˜ð 	 Dð 	¨Tô 	r2   r¢   c                   ór   — e Zd ZdZdedej                  fd„Zd„ Zd„ Z	d„ Z
d„ Zd	„ Zd
ej                  fd„Zy)Ú_Handlerz®Rebota los eventos de disco al event loop. watchdog los llama desde SU hilo, donde `call_later` y
    `create_task` no son thread-safe: todo entra por `call_soon_threadsafe`.ÚorgÚloopc                 ó.   — || _         || _        d | _        y r5   )Ú_orgÚ_loopÚ_timer)r³   rg  rh  s      r    r´   z_Handler.__init__z  s   € ØˆŒ	ØˆŒ
Øˆ�r2   c                 ó>   — |j                   s| j                  «        y y r5   ©Úis_directoryÚ_reset_timer©r³   Úevents     r    Úon_modifiedz_Handler.on_modified  ó   € Ø×!Ò!Ø×ÑÕð "r2   c                 ó>   — |j                   s| j                  «        y y r5   rn  rq  s     r    Ú
on_createdz_Handler.on_createdƒ  rt  r2   c                 ón   — 	 | j                   j                  | j                  «       y # t        $ r Y y w xY wr5   )rk  Úcall_soon_threadsafeÚ	_scheduleÚRuntimeError©r³   s    r    rp  z_Handler._reset_timer‡  s/   € ð	Ø�J‰J×+Ñ+¨D¯N©NÕ;øÜò 	Ùð	ús   ‚%( ¨	4³4c                 óÂ   — | j                   r| j                   j                  «        | j                  j                  t        j
                  | j                  «      | _         y r5   )rl  Úcancelrk  Ú
call_laterr
   ÚWATCHDOG_DEBOUNCE_SECSÚ_firer{  s    r    ry  z_Handler._schedule�  s;   € Ø�;Š;Ø�K‰K×ÑÔ Ø—j‘j×+Ñ+¬F×,IÑ,IÈ4Ï:É:ÓVˆ�r2   c                 ó    — | j                   j                  | j                  j                  «       «      }|j	                  | j
                  «       y r5   )rk  Úcreate_taskrj  rô   Úadd_done_callbackÚ_on_done)r³   Útasks     r    r€  z_Handler._fire’  s5   € Ø�z‰z×%Ñ% d§i¡i×&?Ñ&?Ó&AÓBˆØ×Ñ˜tŸ}™}Õ-r2   r…  c                 óì   — |j                  «       ry |j                  «       }|rQt        j                  d| j                  j
                  ||¬«       t        | j                  j
                  d|› �«       y y )Nu#   [%s] Pasada del watchdog fallÃ³: %srÏ   u   Pasada del watchdog fallÃ³: )Ú	cancelledÚ	exceptionrK   rŸ   rj  r   rC   )r³   r…  rN   s      r    r„  z_Handler._on_done–  s`   € Ø�>‰>ÔØØ�n‰nÓˆÙÜ�L‰LÐ>ÀÇ	Á	×@SÑ@SÐUXÐcfˆLÔgÜ˜$Ÿ)™)×-Ñ-Ð1MÈcÈUÐ/SÕTð r2   N)r_  r`  ra  Ú__doc__r¢   r®   ÚAbstractEventLoopr´   rs  rv  rp  ry  r€  ÚTaskr„  r1   r2   r    rf  rf  v  sM   „ ñPð˜Nð °'×2KÑ2Kó ò
 ò òòWò
.ðU˜WŸ\™\ô Ur2   rf  c           	   ƒ   ó²  K  — t        | «      }d|d<   t        «       |d<   	 t        | «      }|j                  dd¬«       t	        | |t        j                  | «      «      }t        j                  d
| |«       t        | «       d	}	 t        «       }|j                  t        |t!        j"                  «       «      t%        |«      d¬«       |j'                  «        d}	 	 |rt!        j.                  |«      ƒ d	{  –—†  	 |j1                  «       ƒ d	{  –—†  t(        j*                  }ŒI# t        $ rB}t        j                  d| |d¬«       t        | d|› �«       d|d<   t        | «       Y d	}~y	d	}~ww xY w# t        $ rY}t        j                  d| t(        j*                  |«       t        | dt-        t(        j*                  «      › d|› �«       Y d	}~Œød	}~ww xY w7 Œá7 ŒÊ# t         j2                  $ r ‚ t        $ rw}t        | «      d   dz   }t        | d|› �«       t5        t(        j6                  dt5        |dz
  d«      z  z  t8        «      }t        j                  d| |||d¬«       Y d	}~�ŒFd	}~ww xY w# t         j2                  $ r t        j                  d| «       ‚ w xY w# d|d<   t        | «       |�!	 |j;                  «        w # t        $ r Y w w xY ww xY w­w)uS   Bucle de un colegio. **No termina nunca por un error** (ver docstring del mÃ³dulo).Tr)   r*   r§   z&[%s] No se pudo crear el organizer: %srÏ   z!No se pudo iniciar el organizer: FNz[%s] Organizer iniciado en %s)Ú	recursivez5[%s] Watchdog no disponible, solo sondeo cada %ss: %sz$Watchdog no disponible (sondeo cada zs): rª   r.   r<   u   Ciclo del organizer fallÃ³: é   r   uE   [%s] Ciclo del organizer fallÃ³ (%d seguidos), reintento en %.0fs: %sz[%s] Organizer detenido)r8   r   r!   r­   r¢   r   Úschool_rootr   rK   rŸ   rC   r?   r„   r	   Úschedulerf  r®   Úget_running_looprœ   Ústartr
   rW   rc   r€   rô   ÚCancelledErrorÚminÚORGANIZER_RETRY_SECSÚ_BACKOFF_MAXÚstop)r   r@   rn   Ú	organizerrN   ÚobserverÚdelayÚerrorss           r    Úrun_organizerrœ  Ÿ  s¹  è ø€ ä	�YÓ	€BØ€B€w�KÜ!“|€B€|Ñð	Ü# IÓ.ˆØ�‰˜t¨dˆÔ3Ü" 9¨h¼×8QÑ8QÐR[Ó8\Ó]ˆ	ô ‡K�KÐ/°¸HÔEÜ�IÔà€HðtÜ“:ˆØ×Ñœ( 9¬g×.FÑ.FÓ.HÓIÌ3ÈxË=ÐdiÐÔjØ�‰Ôð €EðØÙÜ—m‘m EÓ*×*Ð*ð
KØ×/Ñ/Ó1×1Ð1Ü×2Ñ2�ð øô- ò Ü�‰Ð=¸yÈ#ÐX\ˆÔ]Ü�iÐ#DÀSÀEÐ!JÔKØˆˆ7‰Ü˜	Ô"Üûðûô ò tä�‰ÐLÈiÔY_×YsÑYsÐuxÔyÜ�iÐ#GÌÌF×LfÑLfÓHgÐGhÐhlÐmpÐlqÐ!r×sÑsûðtúð +øà1ùä×)Ñ)ò ØÜò KÜ$ YÓ/Ð0DÑEÈÑI�Ü˜iÐ+GÈÀuÐ)MÔNÜœF×7Ñ7¸1ÄÀFÈQÁJÐPQÓ@RÑ;RÑSÔUaÓb�Ü—‘ÐdØ&¨°°sÀTð ÷ Kò Kûð	Kûô ×!Ñ!ò Ü�‰Ð-¨yÔ9Øðûð ˆˆ7‰Ü˜	Ô"ØÐðØ—‘•øÜò Ùðúð  üsë   ‚K¡>D$ Á$KÂAE2 ÃKÃI1 Ã5GÃ6I1 Ã;G ÄGÄG Ä#I1 Ä$	E/Ä-8E*Å%KÅ*E/Å/KÅ2	GÅ;AGÇ
KÇGÇKÇI1 ÇG ÇI.Ç7A,I)É#I1 É)I.É.I1 É1+JÊJ ÊKÊ3KËKË	KËKËKËKËKc               ƒ   ó&  K  — i } 	 	 	 t        j                  t        j                  d«      ƒ d{  –—† D �ch c]  }|d   ’Œ	 }}t        |«      D ]“  }| j                  |«      }|�|j                  «       sŒ'|�E|j                  «       s5|j                  «       r%t
        j                  d||j                  «       «       t        j                  t        |«      d|› �¬«      | |<   Œ• | D �cg c]	  }||vsŒ|‘Œ c}D ]7  }| j                  |«      j!                  «        t
        j#                  d	|«       Œ9 t        j$                  t&        j(                  «      ƒ d{  –—†  �ŒZ7 �Œ2c c}w # t        $ r,}t
        j                  d|«       t        | «      }Y d}~�ŒUd}~ww xY wc c}w 7 ŒK# t         j*                  $ rS | j-                  «       D ]  }|j!                  «        Œ t        j.                  | j-                  «       d
diŽƒ d{  –—†7   ‚ w xY w­w)zbUn organizer por colegio activo. Detecta colegios nuevos o desactivados sin reiniciar el servicio.TFNr  z4Gestor de organizers: no se pudo listar colegios: %su5   [%s] Organizer terminÃ³ con error: %s â€” se reiniciaz
organizer-)rƒ   z,[%s] Colegio desactivado: organizer detenidoÚreturn_exceptions)r®   ræ   r   Úlist_schoolsr   rK   rŸ   r>   r½   rU   r.  r‡  rˆ  r‚  rœ  Úpopr}  r„   r€   r
   ÚORGANIZER_RESCAN_SECSr“  ÚvaluesÚgather)Útasksrù   ÚactiverN   Úsidr…  Úts          r    Úrun_all_organizersr¨  Ù  sÀ  è ø€ à%'€EðØð$Ü18×1BÑ1BÄ>×C^ÑC^Ð`eÓ1f×+fÖg a˜!˜D›'Ðg�Ðgô ˜f“~ò b�Ø—y‘y “~�Ø�< 4§9¡9¥;ØÐ'°·±Ô0@ÀTÇ^Á^ÔEUÜŸ™Ð%\Ð^aÐcg×cqÑcqÓcsÔtÜ!(×!4Ñ!4´]À3Ó5GÐPZÐ[^ÐZ_ÐN`Ô!a�E˜#’Jðbð $)Ö<˜a¨A°VªOšÒ<ò Q�Ø—	‘	˜#“×%Ñ%Ô'Ü—‘ÐJÈCÕPðQô —-‘-¤× <Ñ <Ó=×=Ð=ñ à+fûÒgøÜò $Ü—‘ÐSÐUXÔYÜ˜U›–ûð$üò =ð >ùÜ×!Ñ!ò Ø—‘“ò 	ˆAØ�H‰H�Jð	ä�n‰n˜eŸl™l›nÐEÀÑE×EÑEØð	üs“   ‚H†F( ˆ'E) ¯E!°E) ¶E$ÁE) Á1F( Á6A3F( Ã)	F!Ã3F!Ã7A#F( ÅF&ÅF( Å!E) Å$E) Å)	FÅ2!FÆF( ÆFÆ	F( Æ(AHÈH	ÈHÈHr5   r^  )Gr‰  r®   rH   ÚloggingÚrer[  r   Úconcurrent.futuresr   r   r   Úpathlibr   Útypingr   rš   Úwatchdog.eventsr   Úwatchdog.observersr	   Úcorer
   Úcore.dbr   Úcore.timeutilr   r   r  r   r   r   r   r   Ú	getLoggerr_  rK   ÚCV2_AVAILABLEr”  Ú
__import__Ú	cpu_countrÑ   ÚIMAGE_EXTENSIONSrs   rt   rÃ   r¹   r  r–  rœ   r!   r"   rG   Ú__annotations__r3   r8   r`   rC   r?   rZ   ri   rm   rc   ra   r‰   r—   rb  r    r¢   rf  rœ  r¨  )Úes   0r    ú<module>rº     s  ðòó: Û Û Û 	Û Û ß ?Ý Ý Ý ã 
Ý 2Ý 'å Ý ß ,ß bÕ bà	ˆ×	Ñ	˜8Ó	$€à€áˆq‘:˜dÓ#×-Ñ-Ó/Ò4°1Ó6€Ø×*Ñ*Ð Ø(8Ö9 1�Q—W‘W•YÒ9Ð à�8Ð Ø€ð Ð Ø€ð

+ ð 
+¨ó 
+ð &(Ð �4˜˜T˜	‘?Ó 'ð�Tó ðB˜3ð B 4ó Bñ˜Sð ¨3ð °xÀ±~ð ÐQUó ð
M˜sð 
M tó 
Mð˜sð  x°¡~ó ð Cð ¨Dó ñ@+˜( 3™-ð +°4ó +ð˜Tð  có ð˜$ð  4ó ð2˜ð  (¨8Ñ"4ó ñ 
'˜ð 
'Ð(9ð 
'ÀUó 
'÷|ñ |ôB&UÐ%ô &UðR7 3ð 7¨4ó 7ôtùòC :s   Â-E1