Ë
    pÀjå,  ã                  ó  — d dl mZ d dlZd dlZd dlmZ d dlmZ d dlmZm	Z	 d dl
mZ d dlmZ dZd	Zd
dddœZ ed¬«       G d„ d«      «       Zdd„Zdd„Zdd„Zdd„Zd d„Zd!d„Zd"d„Zd#d„Zd$d„Zedk(  r e«        yy)%é    )ÚannotationsN)Ú	dataclass)Údatetime)Úcreate_engineÚtext)Ú
Connection)Údatabase_urlÚbf4swéd   ÚpcÚps4Úxboxone)ÚPCzPS4/5ÚXBoxT)Úfrozenc                  óJ   — e Zd ZU ded<   ded<   ded<   ded<   ded<   ded	<   y
)ÚAliasRowÚintÚalias_idÚstrÚplatformÚ
persona_idÚplayer_namer   Ú
first_seenÚ	last_seenN)Ú__name__Ú
__module__Ú__qualname__Ú__annotations__© ó    úbf4ps/discovery.pyr   r      s#   … àƒMØƒMØƒOØÓØÓØÔr!   r   c                 ó^   — t         j                  j                  d«      } | st        d«      ‚| S )NÚBF4PS_BF4SW_DATABASE_URLz$BF4PS_BF4SW_DATABASE_URL is required)ÚosÚenvironÚgetÚRuntimeError)Úvalues    r"   Úbf4sw_database_urlr*       s)   € Ü�J‰J�N‰NÐ5Ó6€EÙÜÐAÓBÐBØ€Lr!   c           	     óH   — t        | d   | d   | d   | d   | d   | d   ¬«      S )NÚidr   r   r   r   r   )r   r   r   r   r   r   )r   )Úrows    r"   Ú_alias_from_mappingr.   '   s=   € ÜØ�T‘Ø�Z‘Ø�|Ñ$Ø˜Ñ&Ø�|Ñ$Ø�kÑ"ôð r!   c               óØ   — | j                  t        d«      ||dœ«      j                  «       j                  «       }|st	        d|›d|› �«      ‚|D �cg c]  }t        |«      ‘Œ c}S c c}w )Nz÷
            SELECT id, platform, persona_id, player_name, first_seen, last_seen
            FROM bf4_player_aliases
            WHERE persona_id = :persona_id
              AND platform = :platform
            ORDER BY first_seen, id
            ©r   r   z%BF4SW aliases not found for platform=z, persona_id=)Úexecuter   ÚmappingsÚallÚLookupErrorr.   )Úsourcer   r   Úrowsr-   s        r"   Úload_aliasesr7   2   ss   € Ø�>‰>Üðó	
ð "¨xÑ8ó÷ �hƒj—‘“ð 	ñ ÜÐAÀ(ÀÈ]Ð[eÐZfÐgÓhÐhØ04Ö5¨Ô Õ$Ò5Ð5ùÒ5s   ÁA'c               ó²   — | j                  t        d«      ||dœ«      j                  «       j                  «       }|D �cg c]  }t	        |«      ‘Œ c}S c c}w )NzÞ
            SELECT id, platform, persona_id, player_name, first_seen, last_seen
            FROM bf4_player_aliases
            WHERE id > :after_alias_id
            ORDER BY id
            LIMIT :batch_size
            ©Úafter_alias_idÚ
batch_size)r1   r   r2   r3   r.   )r5   r:   r;   r6   r-   s        r"   Úload_discovery_batchr<   D   sU   € Ø�>‰>Üðó	
ð *¸ÑDó÷ �hƒj—‘“ð 	ð 15Ö5¨Ô Õ$Ò5Ð5ùÒ5s   ¿Ac           	     óö  — |st        d«      ‚|D �ch c]  }|j                  |j                  f’Œ }}t        |«      dk7  rt        d«      ‚t	        t        |«      «      \  }}t        j                  |«      }|€t        d|›�«      ‚t        d„ |D «       «      }t        d„ |D «       «      }t        |d„ ¬«      }	| j                  t        d	«      |||	j                  ||d
œ«      j                  «       }
|D ]@  }| j                  t        d«      |
|j                  |j                  |j                  dœ«       ŒB | j                  t        d«      |
||dœ«       | j                  t        d«      d|
i«       |
S c c}w )Nz$At least one BF4SW alias is requiredé   z=All aliases must belong to the same platform/persona identityzUnsupported BF4SW platform c              3  ó4   K  — | ]  }|j                   –— Œ y ­w©N)r   ©Ú.0Úaliass     r"   ú	<genexpr>z!import_aliases.<locals>.<genexpr>a   s   è ø€ Ò>¨U˜×(Õ(Ñ>ùó   ‚c              3  ó4   K  — | ]  }|j                   –— Œ y ­wr@   )r   rA   s     r"   rD   z!import_aliases.<locals>.<genexpr>b   s   è ø€ Ò;¨%�e—o•oÑ;ùrE   c                ó2   — | j                   | j                  fS r@   ©r   r   ©rC   s    r"   ú<lambda>z import_aliases.<locals>.<lambda>c   s   € °E·O±OÀUÇ^Á^Ð3T€ r!   ©Úkeya  
            INSERT INTO soldiers (
                persona_id, platform, current_name, first_seen_at, last_seen_at
            )
            VALUES (
                :persona_id, :platform, :name, :first_seen, :last_seen
            )
            ON CONFLICT (persona_id, platform) DO UPDATE SET
                current_name = CASE
                    WHEN EXCLUDED.last_seen_at >= soldiers.last_seen_at
                    THEN EXCLUDED.current_name
                    ELSE soldiers.current_name
                END,
                first_seen_at = LEAST(soldiers.first_seen_at, EXCLUDED.first_seen_at),
                last_seen_at = GREATEST(soldiers.last_seen_at, EXCLUDED.last_seen_at),
                updated_at = now()
            RETURNING soldier_id
            )r   r   Únamer   r   a¬  
                INSERT INTO soldier_names (soldier_id, name, first_seen_at, last_seen_at)
                VALUES (:soldier_id, :name, :first_seen, :last_seen)
                ON CONFLICT (soldier_id, name) DO UPDATE SET
                    first_seen_at = LEAST(soldier_names.first_seen_at, EXCLUDED.first_seen_at),
                    last_seen_at = GREATEST(soldier_names.last_seen_at, EXCLUDED.last_seen_at)
                )Ú
soldier_idrM   r   r   aª  
            INSERT INTO soldier_sources (soldier_id, source_type, first_seen_at, last_seen_at)
            VALUES (:soldier_id, 'bf4sw', :first_seen, :last_seen)
            ON CONFLICT (soldier_id, source_type) DO UPDATE SET
                first_seen_at = LEAST(soldier_sources.first_seen_at, EXCLUDED.first_seen_at),
                last_seen_at = GREATEST(soldier_sources.last_seen_at, EXCLUDED.last_seen_at)
            )rN   r   r   z”
            INSERT INTO collection_state (soldier_id)
            VALUES (:soldier_id)
            ON CONFLICT (soldier_id) DO NOTHING
            rN   )Ú
ValueErrorr   r   ÚlenÚnextÚiterÚPLATFORM_MAPr'   ÚminÚmaxr1   r   r   Ú
scalar_oner   r   )ÚdestinationÚaliasesrC   ÚidentityÚsource_platformr   r   Úearliest_seenÚlatest_seenÚcurrent_aliasrN   s              r"   Úimport_aliasesr^   T   s¬  € ÙÜÐ?Ó@Ð@à@GÖH°u�—‘ ×!1Ñ!1Ò2ÐH€HÐHÜ
ˆ8ƒ}˜ÒÜÐXÓYÐYä"&¤t¨H£~Ó"6Ñ€O�ZÜ×Ñ Ó0€HØÐÜÐ6°Ð6IÐJÓKÐKäÑ>°gÔ>Ó>€MÜÑ;°7Ô;Ó;€KÜ˜Ñ%TÔU€Mà×$Ñ$Üðó	
ð* %Ø Ø!×-Ñ-Ø'Ø$ñ	
ó+÷8 �jƒlð9 ð< ò 
ˆØ×ÑÜðóð )Ø×)Ñ)Ø#×.Ñ.Ø"Ÿ_™_ñ	õ	
ð
ð& ×ÑÜðó	
ð %Ø'Ø$ñ	
ôð" ×ÑÜðó	
ð 
�zÐ"ô	ð Ðùòu Is   ’E6c                ó¢   — | j                  t        d«      dt        i«       | j                  t        d«      dt        i«      j                  «       S )Nz¨
            INSERT INTO discovery_state (source_name, last_alias_id)
            VALUES (:source_name, 0)
            ON CONFLICT (source_name) DO NOTHING
            Úsource_namezJSELECT last_alias_id FROM discovery_state WHERE source_name = :source_name)r1   r   ÚSOURCE_NAMErV   )rW   s    r"   Úensure_discovery_staterb   µ   sV   € Ø×ÑÜðó	
ð 
œÐ$ô	ð ×ÑÜÐYÓZØ	œÐ$ó÷ �jƒlðr!   c               ó*  — |dk  rt        d«      ‚t        |«      }|j                  t        d«      dt        i«       t        | ||¬«      }|s'|j                  t        d«      dt        i«       ||ddfS t        |D �ch c]  }|j                  |j                  f’Œ c}«      }|D ]  \  }}t        | ||¬«      }	t        ||	«       Œ! |d	   j                  }
|j                  t        d
«      t        |
dœ«       ||
t        |«      t        |«      fS c c}w )Nr>   zbatch_size must be at least 1z–
            UPDATE discovery_state
            SET last_poll_at = now(), updated_at = now()
            WHERE source_name = :source_name
            r`   r9   z¼
                UPDATE discovery_state
                SET last_success_at = now(), last_error = NULL, updated_at = now()
                WHERE source_name = :source_name
                r   r0   éÿÿÿÿzü
            UPDATE discovery_state
            SET last_alias_id = :last_alias_id,
                last_success_at = now(),
                last_error = NULL,
                updated_at = now()
            WHERE source_name = :source_name
            )r`   Úlast_alias_id)rO   rb   r1   r   ra   r<   Úsortedr   r   r7   r^   r   rP   )r5   rW   r;   re   ÚbatchrC   Ú
identitiesr   r   rX   Únew_last_alias_ids              r"   Úrun_discovery_batchrj   Æ   s6  € Ø�A‚~ÜÐ8Ó9Ð9ä*¨;Ó7€MØ×ÑÜðó	
ð 
œÐ$ô	ô ! ¸ÐR\Ô]€EÙØ×ÑÜðóð œKÐ(ô		
ð ˜m¨Q°Ð1Ð1äÈÖOÀ˜%Ÿ.™.¨%×*:Ñ*:Ò;ÒOÓP€JØ *ò -Ñˆ�*Ü˜v°*ÀxÔPˆÜ�{ GÕ,ð-ð ˜b™	×*Ñ*ÐØ×ÑÜðó		
ô $Ð6GÑHôð Ð+¬S°«Z¼¸Z»ÐHÐHùò' Ps   Á=Dc               óX  — d}d}d}	 | j                  «       5 }|j                  «       5 }t        |||¬«      \  }}	}
}d d d «       d d d «       
dk(  rt        d	› d|› d|› d|› �«       y |dz  }||
z  }|z  }t        d|› d	› d
	› d|
› d|› �
«       ŒŒ# 1 sw Y   ŒXxY w# 1 sw Y   Œ\xY w)Nr   ©r;   z$BF4SW discovery caught up at cursor=z
: batches=ú alias_rows=z identity_visits=r>   zBatch z	: cursor=ú->ú identities=)ÚconnectÚbeginrj   Úprint)Úsource_engineÚdestination_enginer;   Úbatch_numberÚtotal_alias_rowsÚtotal_identitiesr5   rW   Ú
old_cursorÚ
new_cursorÚ
alias_rowsrh   s               r"   Úrun_catch_upr{   ú   s  € Ø€LØÐØÐà
Ø×"Ñ"Ó$ð 	¨Ø#×)Ñ)Ó+ð ¨{ÜATØ˜K°JôBÑ>�
˜J¨
°J÷÷	ð ˜Š?ÜØ6°z°lð CØ'˜.¨Ð5EÐ4Fð G#Ø#3Ð"4ð6ôð
 à˜ÑˆØ˜JÑ&ÐØ˜JÑ&ÐÜØ�\�N )¨J¨<°r¸*¸ð FØ$˜ \°*°ð?ô	
ð% ÷ð ú÷	ð 	ús!   ˜B ©B½B ÂB	ÂB Â B)c                 ó2  — t        j                  d¬«      } | j                  d¬«      }|j                  dt        d¬«       |j                  dd	d
¬«       |j                  dd	d¬«       | j                  dt        t        «      d¬«       | j                  dt        t        dt        › d�¬«       | j                  «       }t        t        «       «      }t        t        «       «      }|j                  �Í|j                  €| j                  d«       |j                  «       5 }t!        ||j                  |j                  ¬«      }d d d «       |j#                  «       5 }t%        |«      }d d d «       t'        d„ ¬«      }	t)        d|	j                  › d|	j                  › dt+        |«      › d|	j,                  ›d› �
«       y |j                  �| j                  d«       |j.                  rt1        |||j2                  ¬«       y |j                  «       5 }|j#                  «       5 }t5        |||j2                  ¬«      \  }
}}}d d d «       d d d «       t)        d 
› d!› d"› d#› �«       y # 1 sw Y   �Œ(xY w# 1 sw Y   �ŒxY w# 1 sw Y   ŒCxY w# 1 sw Y   ŒGxY w)$Nz!Import BF4SW soldiers into BF4PS.)ÚdescriptionT)Úrequiredz--persona-idz:Import one known BF4SW persona and complete alias history.)ÚtypeÚhelpz
--discoverÚ
store_truez&Run one bounded BF4SW discovery batch.)Úactionr€   z--discover-until-caught-upzHRun bounded discovery batches until BF4SW has no rows beyond the cursor.z
--platformz+BF4SW platform; required with --persona-id.)Úchoicesr€   z--batch-sizez)Alias rows per discovery batch (default: z).)r   Údefaultr€   z(--platform is required with --persona-idr0   c                ó2   — | j                   | j                  fS r@   rH   rI   s    r"   rJ   zmain.<locals>.<lambda>+  s   € ¸¿¹ÈÏÉÐ7X€ r!   rK   zImported BF4SW persona_id=z
 platform=z	 aliases=z current_name=z as BF4PS soldier_id=z*--platform is only valid with --persona-idrl   z'BF4SW discovery batch complete: cursor=rn   rm   ro   )ÚargparseÚArgumentParserÚadd_mutually_exclusive_groupÚadd_argumentr   rf   rS   ÚDEFAULT_BATCH_SIZEÚ
parse_argsr   r*   r	   r   r   Úerrorrp   r7   rq   r^   rU   rr   rP   r   Údiscover_until_caught_upr{   r;   rj   )ÚparserÚmodeÚargsrs   rt   r5   rX   rW   rN   r]   rx   ry   rz   rh   s                 r"   Úmainr‘     s¢  € Ü×$Ñ$Ð1TÔU€FØ×.Ñ.¸Ð.Ó=€DØ×Ñ�n¬3Ð5qÐÔrØ×Ñ�l¨<Ð>fÐÔgØ×ÑÐ2¸<ð  OYÐô  ZØ
×Ñ˜¬f´\Ó.BÐIvÐÔwØ
×Ñ˜¬SÔ:LÐU~ô  @Rð  Sð  SUð  TVÐô  WØ×ÑÓ€Dä!Ô"4Ó"6Ó7€MÜ&¤|£~Ó6Ðà‡�Ð"Ø�=‰=Ð Ø�L‰LÐCÔDØ×"Ñ"Ó$ð 	_¨Ü" 6°d·o±oÐPT×P]ÑP]Ô^ˆG÷	_à×%Ñ%Ó'ð 	>¨;Ü'¨°WÓ=ˆJ÷	>ä˜GÑ)XÔYˆÜØ(¨×)AÑ)AÐ(BÀ*È]×McÑMcÐLdð eÜ˜7“|�n N°=×3LÑ3LÐ2Oð P#Ø#- ,ð0ô	
ð
 	à‡}�}Ð Ø�‰ÐAÔBà×$Ò$Ü�]Ð$6À4Ç?Á?ÕSØà	×	Ñ	Ó	 ð  FØ×%Ñ%Ó'ð 	¨;Ü=PØ˜°·±ô>Ñ:ˆJ˜
 J°
÷	÷ô 
Ø
1°*°¸RÀ
¸|ð LØ �\ ¨j¨\ð	;õ÷3	_ñ 	_ú÷	>ñ 	>ú÷$	ð 	ú÷ð ús<   Ä#I'ÅI4ÈJÈ!JÈ?JÉ'I1É4I>ÊJ
	ÊJÊJÚ__main__)Úreturnr   )r“   r   )r5   r   r   r   r   r   r“   úlist[AliasRow])r5   r   r:   r   r;   r   r“   r”   )rW   r   rX   r”   r“   r   )rW   r   r“   r   )r5   r   rW   r   r;   r   r“   ztuple[int, int, int, int])r;   r   r“   ÚNone)r“   r•   )Ú
__future__r   r†   r%   Údataclassesr   r   Ú
sqlalchemyr   r   Úsqlalchemy.enginer   Úbf4ps.configr	   ra   rŠ   rS   r   r*   r.   r7   r<   r^   rb   rj   r{   r‘   r   r    r!   r"   ú<module>r›      sœ   ðÝ "ã Û 	Ý !Ý ç *Ý (å %à€ØÐ à
ØØñ€ñ �$Ô÷ð ó ðóóó6ó$6ó ^óBó"1Ióh
ó:,ð^ ˆzÒÙ…Fð r!   