o
    \CjI                     @   s>  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ZddlmZ ddl	m
Z
mZmZmZ ddlmZ ddlZddlZeejj zddlmZ ddlmZ W n eye   ed ed	 Y nw d
d Ze ZG dd dZG dd dZG dd dZ G dd dZ!G dd dZ"dd Z#e$dkre#  dS dS )a/  
ADMS Local Sync Agent

Syncs user data from cloud ADMS to local ZKTeco biometric devices.
Runs as a background service and periodically fetches user data from cloud
and distributes it to all local devices on the network.

Configuration:
- Set CLOUD_URL environment variable or in config file
- Set SYNC_INTERVAL in config (default: 10 minutes)
- Set device IP addresses in config or auto-discover

Usage:
    python3 sync_agent.py
    python3 sync_agent.py --config /path/to/config.json
    python3 sync_agent.py --device 192.168.1.100  (test single device)
    N)datetime)ListDictOptionalTuple)Path)BackgroundScheduler)IntervalTriggerzFERROR: apscheduler not installed. Run: pip install -r requirements.txt   c                  C   sZ   t d} | jdd | dt d d }tjtjddt|t	 gd	 t
tS )
zConfigure logginglogsT)exist_oksync_agent_z%Y-%m-%dz.logz'%(asctime)s [%(levelname)s] %(message)sz%Y-%m-%d %H:%M:%S)levelformatdatefmthandlers)r   mkdirr   nowstrftimeloggingbasicConfigINFOFileHandlerStreamHandler	getLogger__name__)log_dirlog_file r   sync_agent.pysetup_logging0   s   

r    c                   @   sJ   e Zd ZdZddee fddZdefddZdd	efd
dZ	dd Z
dS )ConfigzConfiguration managementNconfig_filec                 C   s   |pd| _ |  | _d S )Nsync_agent_config.json)r"   _load_configdata)selfr"   r   r   r   __init__M   s   
zConfig.__init__returnc              
   C   s   t dddddg dddd	}t| j r]z+t| jd
}t|}W d   n1 s-w   Y  || t	
d| j  W n ty\ } zt	d| d W Y d}~nd}~ww t d|d |d< tt d|d |d< |S )z+Load configuration from file or environmentADMS_CLOUD_URLhttp://localhost
         T	192.168.1  )	cloud_urlsync_interval_minutesdevice_timeout_secondsmax_retriesdevicesauto_discovernetwork_prefixportrNu   ✓ Loaded configuration from zCould not load config file: z. Using defaults.r0   SYNC_INTERVALr1   )osgetenvr   r"   existsopenjsonloadupdateloggerinfo	Exceptionwarningint)r&   default_configffile_configer   r   r   r$   Q   s.   

zConfig._load_configkeyc                 C   s   | j ||S )zGet config value)r%   get)r&   rJ   defaultr   r   r   rK   p   s   z
Config.getc              
   C   sr   ddddddddd	d
dgddddd	}t dd}tj||dd W d   n1 s-w   Y  td dS )zCreate a template config filezhttps://your-adms.cloud.comr+   r,   r-   z192.168.1.100z192.168.1.101device_password_here)ippasswordz192.168.1.102another_device_passwordTr.   i  zpEdit this file and set cloud_url. For devices with password, use object format: {"ip": "...", "password": "..."})	r0   r1   r2   r3   r4   r5   r6   r7   notesr#   w   )indentNu<   ✓ Created sync_agent_config.json template. Please edit it.)r=   r>   dumprA   rB   )r&   templaterG   r   r   r   save_templatet   s*   zConfig.save_templateN)r   
__module____qualname____doc__r   strr'   r   r$   rK   rW   r   r   r   r   r!   J   s    r!   c                   @   s   e Zd ZdZddedefddZdeded	ee fd
dZ	d	ee
e  fddZd	ee
e  fddZd	ee fddZded	efddZd	efddZdS )CloudAPIClientz(Client for communicating with cloud ADMSr+   base_urltimeoutc                 C   s   | d| _|| _d S )N/)rstripr^   r_   )r&   r^   r_   r   r   r   r'      s   
zCloudAPIClient.__init__methodendpointr(   c              
   K   sx   | j  d| }ztj||fd| ji|}|  | W S  tjjy; } zt	d|  W Y d}~dS d}~ww )zMake HTTP requestz/apir_   u   ❌ API request failed: N)
r^   requestsrequestr_   raise_for_statusr>   
exceptionsRequestExceptionrA   error)r&   rb   rc   kwargsurlresponserI   r   r   r   _request   s"   
zCloudAPIClient._requestc                 C   *   |  dd}|r|dr|dg S dS )zGet all users from cloudGETz/sync/userssuccessusersNrm   rK   r&   resultr   r   r   	get_users      zCloudAPIClient.get_usersc                 C   rn   )zGet all devices from cloudro   z/sync/devicesrp   r4   Nrr   rs   r   r   r   get_devices   rv   zCloudAPIClient.get_devicesc                 C   s*   |  dd}|r|dr|di S dS )zGet sync statusro   z/sync/statusrp   statusNrr   rs   r   r   r   get_sync_status   rv   zCloudAPIClient.get_sync_statusreportc                 C   s    | j dd|d}|o|ddS )zReport sync completionPOSTz/sync/report)r>   rp   Frr   )r&   rz   rt   r   r   r   report_sync   s   zCloudAPIClient.report_syncc                 C   s   |  dd}|o|ddS )zCheck cloud API healthro   z/sync/healthrp   Frr   rs   r   r   r   health_check   s   zCloudAPIClient.health_checkN)r+   )r   rY   rZ   r[   r\   rE   r'   r   r   rm   r   ru   rw   ry   boolr|   r}   r   r   r   r   r]      s    r]   c                	   @   sL   e Zd ZdZeddededefddZedd
edede	e fddZ
dS )DeviceDiscoveryz(Discover ZKTeco devices on local networkrS   rN   r_   r(   c              	   C   s>   zt j| df|d}|  W dS  t jt jfy   Y dS w )z.Check if device is reachable on HTTPS port 443r/   r_   TF)socketcreate_connectioncloser_   ri   )rN   r_   sockr   r   r   ping   s   zDeviceDiscovery.pingr.      r6   	max_hostsc                 C   s   g }t d|  d tdt|d dD ]}|  d| }tj|ddr2|| t d|  qt dt| d	 |S )
z Auto-discover devices on networku!   🔍 Auto-discovering devices on z.0/24...r
      .r   u      ✓ Found device: u   ✓ Discovery complete. Found z	 devices.)rA   rB   rangeminr   r   appendlen)r6   r   r4   irN   r   r   r   r5      s   
zDeviceDiscovery.auto_discoverN)rS   )r.   r   )r   rY   rZ   r[   staticmethodr\   rE   r~   r   r   r5   r   r   r   r   r      s    	"r   c                
   @   sN   e Zd ZdZddedededee fd	d
Zdee	 de
eef fddZdS )
DeviceSyncz)Sync user data to ZKTeco device via HTTPSr/   r,   N	device_ipr7   r_   rO   c                 C   s   || _ || _|| _|| _d S rX   )r   r7   r_   rO   )r&   r   r7   r_   rO   r   r   r   r'      s   
zDeviceSync.__init__rq   r(   c              
   C   s"  zFddd |D i}| j r| j |d< d| j d| j d}tj||| jdd	}|jd
kr:ddt| d| j fW S dd|j d|j fW S  tj	j
yZ   dd| j df Y S  tj	jyv } zdd| j dfW  Y d}~S d}~w ty } zddt| fW  Y d}~S d}~ww )z1Sync users to device via HTTPS (PC Communication)rq   c              	   S   s@   g | ]}|d  | dd| dpt|d  | dddqS )idnameUnknownuser_idemail )r   r   r   r   )rK   r\   ).0ur   r   r   
<listcomp>   s    

z)DeviceSync.sync_users.<locals>.<listcomp>rO   zhttps://:z/iclock/userF)r>   r_   verify   Tu   ✅ Successfully synced z
 users to zHTTPS z: u   ⏱️  HTTPS timeout - device z not respondingu   ❌ Cannot connect to z*:443 - check if HTTPS is enabled on deviceNzError: )rO   r   r7   rd   postr_   status_coder   textrg   TimeoutConnectionErrorrC   r\   )r&   rq   	user_datark   rl   rI   r   r   r   
sync_users   s4   

zDeviceSync.sync_users)r/   r,   N)r   rY   rZ   r[   r\   rE   r   r'   r   r   r   r~   r   r   r   r   r   r      s     "r   c                   @   s<   e Zd ZdZdefddZdd Zdd Zd	efd
dZ	dS )	SyncAgentzMain sync agentconfigc                 C   s.   || _ t|d| _t | _d| _d | _d S )Nr0   r   )r   r]   rK   cloudr   	scheduler
sync_count
last_error)r&   r   r   r   r   r'   )  s
   
zSyncAgent.__init__c              
      s  zSt d t d t d t }| j std| j }|du r+tdt dt| d |s@t d W dS | j	 }|du rKg }| j
d	g }|sj| j
d
rjt d tj| j
ddd}|stt d W dS t dt| d d}d}|D ]}t|tr|d |d}n| d}t d  d t | j
d| j
d|d}	|	|\}
}|
rt d|  |d7 }d}|D ]}|d kr|d} nq|r|t fdd|D d d!t|t | t  d"}| j|rt d# qt d$|  |d7 }qt | }t d% t d& t d'|  t d(|  t d)|d*d+ t d, |  jd7  _d| _W dS  tyu } zt d-|  t|| _W Y d}~dS d}~ww ).zMain sync cyclezP================================================================================u   🔄 STARTING SYNC CYCLEzCloud API is unreachableN Failed to fetch users from cloudu   📥 Fetched z users from cloudu   ⚠️  No users to syncr4   r5   u    🔍 Auto-discovering devices...r6   r   )r   u    ⚠️  No devices found to syncu   📡 Syncing to z
 device(s)r   rN   rO   z
   Syncing to ...r7   r2   )rO   u      ✅ r
   	ip_devicer   c                 3   s(    | ]}| d  kr| dV  qdS )r   no_snN)rK   )r   dr   r   r   	<genexpr>  s   & z'SyncAgent.sync_cycle.<locals>.<genexpr>unknownrp   )	device_id	device_snrx   users_synced	sync_time	timestampu%      📤 Reported sync status to cloudu      ❌ zQ
================================================================================u   ✨ SYNC CYCLE COMPLETEu      ✅ Successful: u      ❌ Failed: u      ⏱️  Duration: z.1fszQ================================================================================
u   ❌ Sync cycle failed: )rA   rB   timer   r}   rC   ru   r   rD   rw   r   rK   r   r5   
isinstancedictr   r   nextr   r   	isoformatr|   ri   r   r   r\   )r&   sync_start_timerq   devices_from_clouddevices_to_sync
successfulfaileddevice_entrydevice_passworddevicerp   messager   r   rz   sync_durationrI   r   r   r   
sync_cycle0  s   
















	




zSyncAgent.sync_cyclec              
   C   s&  zr| j dd}td td| j d  td| d td| j d	 d
 | jj| jt|ddddd td |   | j  td z	 t	
d qS tyr   td | j  td Y W dS w  ty } ztd|  td W Y d}~dS d}~ww )zStart the agentr1   r+   u   🚀 Starting sync agentz   Cloud URL: r0   z   Sync interval: z minutesz   Device timeout: r2   r   )minutesr   zUser Sync CycleT)triggerr   r   replace_existingu"   🔄 Running initial sync cycle...u.   ✅ Sync agent running. Press Ctrl+C to stop.
r
   u   
⏹️  Stopping sync agent...u   ✓ Sync agent stoppedzFailed to start sync agent: N)r   rK   rA   rB   r   add_jobr   r	   startr   sleepKeyboardInterruptshutdownrC   ri   sysexit)r&   intervalrI   r   r   r   r     s>   

	




zSyncAgent.startr   c                 C   sz   t d| d | j }|st d dS t|| jd}||\}}|r3t d|  dS t d|  dS )zTest sync to a single deviceu   🧪 Testing sync to r   r   Nr7   u   ✅ Success: u   ❌ Failed: )	rA   rB   r   ru   ri   r   r   rK   r   )r&   r   rq   r   rp   r   r   r   r   test_device  s   

zSyncAgent.test_deviceN)
r   rY   rZ   r[   r!   r'   r   r   r\   r   r   r   r   r   r   &  s    p(r   c                  C   s   t jdd} | jdddd | jddd	 | jd
ddd |  }t|j}|jr/|  d S |dr;|ddkrTt	
d t	
d t	
d t	
d td t|}|jrc||j d S |  d S )Nz?ADMS Local Sync Agent - Syncs users from cloud to local devices)descriptionz--configzPath to configuration filer#   )helprL   z--devicezTest sync to specific device IP)r   z--create-config
store_truezCreate configuration template)actionr   r0   r*   u"   ❌ ADMS_CLOUD_URL not configured!zN   Set environment variable: export ADMS_CLOUD_URL=https://your-adms.cloud.comz3   Or edit sync_agent_config.json and set cloud_urlz-   Run: python3 sync_agent.py --create-configr
   )argparseArgumentParseradd_argument
parse_argsr!   r   create_configrW   rK   rA   ri   r   r   r   r   r   r   )parserargsr   agentr   r   r   main  sB   





r   __main__)%r[   r:   r   r>   rd   r   r   r   r   typingr   r   r   r   pathlibr   r   urllib3disable_warningsrg   InsecureRequestWarning!apscheduler.schedulers.backgroundr   apscheduler.triggers.intervalr	   ImportErrorprintr   r    rA   r!   r]   r   r   r   r   r   r   r   r   r   <module>   sB   	L<7 91
