U
    =NhH                     @   s,  d dl Z d dlZd dlZd dlmZmZ d dlmZmZmZ d dl	Z	d dl
Z
d dlmZmZ d dlZd dlZe
eZG dd dZd dl Z d dlmZ d dlmZmZmZ d dlmZ G d	d
 d
Zd dlmZmZmZmZmZmZmZ d dlmZ d dl m!Z! e Z"G dd de"Z#G dd de"Z$dS )    N)datetime	timedelta)ListDictOptional)ThreadPoolExecutoras_completedc                   @   s   e Zd ZdZdddZee dddZeee	e
 dd	d
Ze
e	e dddZdeee ee
 dddZee
 dddZee
 dddZdeeeee
 dddZee
 ee
 dddZdS )DeveloperDiscoveryzKAutomatically discover ActivityWatch instances on network and local machineNc                 C   s&   || _ g | _dddddg| _d| _d S )N  i  i  i  iD     )
db_sessiondiscovered_developersdefault_portstimeout)selfr    r   ./dynamic_discovery.py__init__   s    zDeveloperDiscovery.__init__returnc              
   C   s   g }zt  D ]v}t |}t j|kr|t j D ]R}d|kr0d|kr0|d }|ds0|ds0d|ddd }|| q0qW n> tk
r } z t	
d|  d	d
ddg}W 5 d}~X Y nX tt|S )z$Get all local network ranges to scanZaddrZnetmaskz127.z169.254..Nz"Could not get network interfaces: z	192.168.1z	192.168.0z10.0.0z172.16.0)	netifacesZ
interfacesZifaddressesZAF_INET
startswithjoinsplitappend	ExceptionloggerZwarninglistset)r   networksZ	interfaceZaddrsZ	addr_infoZipZbase_iper   r   r   get_local_networks   s    

z%DeveloperDiscovery.get_local_networks)hostportr   c           
      C   s,  zd| d| d}t j|| jd}|jdkr| }t jd| d| d| jd}|jdkrf| ni }| |pv|}| d| ||||dd	|d
||d| d| t| t|d| t	
  ddW S W nF tk
r& }	 z&td| d| d|	  W Y dS d}	~	X Y nX dS )z7Check if ActivityWatch is running on specific host:porthttp://:/api/0/infor      z/api/0/buckets_versionunknownhostname	device_idzActivityWatch on online)idnamer$   r%   r,   r.   r/   bucketsbucket_countdescriptiondiscovered_atstatuszNo ActivityWatch at : N)requestsgetr   status_codejsonextract_hostname_from_bucketsr   keyslenr   now	isoformatr   r   debug)
r   r$   r%   urlresponseinfoZbuckets_responser3   r.   r"   r   r   r   check_activitywatch_instance.   s0    




z/DeveloperDiscovery.check_activitywatch_instance)r3   r   c                 C   s<   |  D ].}d|kr|d}t|dkr|d   S qdS )z"Extract hostname from bucket namesr+      r   N)r>   r   r?   )r   r3   bucket_namepartsr   r   r   r=   P   s    
z0DeveloperDiscovery.extract_hostname_from_buckets)network_baseportsr   c                    s   |dkrj }g }fdd g }tddD ]*}| d| }|D ]}|||f qBq,tddf fd	d
|D }t|D ]B}	|	 }
|
r||
 td|
d  d|
d  d|
d   qW 5 Q R X |S )z0Scan a network range for ActivityWatch instancesNc                    s    | \}}  ||}|r|S d S N)rF   )Z	host_portr$   r%   resultr   r   r   check_host_porta   s
    z>DeveloperDiscovery.scan_network_range.<locals>.check_host_portrG      r   2   max_workersc                    s   i | ]}  ||qS r   )Zsubmit).0Zhp)rO   executorr   r   
<dictcomp>q   s   
 z9DeveloperDiscovery.scan_network_range.<locals>.<dictcomp>zDiscovered ActivityWatch: r2   z at r$   r'   r%   )r   ranger   r   r   rM   r   rE   )r   rJ   rK   
discoveredZhost_port_combinationsir$   r%   Zfuture_to_host_portZfuturerM   r   )rO   rU   r   r   scan_network_rangeZ   s&    
4z%DeveloperDiscovery.scan_network_rangec                 C   s   g }dddg}|D ]h}| j D ]\}| ||}|r|d dkrjt |d< t |d< t  d| |d< ||  qqq|S )	z-Discover ActivityWatch instances on localhost	127.0.0.1	localhost0.0.0.0r2   )r[   r\   r]   r.   r+   r1   )r   rF   socketZgethostnamer   )r   rX   Zlocalhost_addressesr$   r%   rM   r   r   r   discover_local_instances~   s    


z+DeveloperDiscovery.discover_local_instancesc                 C   s   | j s
g S zddlm} |d}t tdd }| j |d|i}g }|D ]V}||j|j	pb|jdd|j	pp|j|jd	|j
 d
|jr|j nd|j
ddd qL|W S  tk
r } ztd|  g  W Y S d}~X Y nX dS )z2Discover developers from database activity recordsr   )texta  
                SELECT DISTINCT 
                    developer_id,
                    developer_name,
                    MAX(created_at) as last_seen,
                    COUNT(*) as activity_count
                FROM activity_records 
                WHERE developer_id IS NOT NULL 
                    AND created_at > :last_month
                GROUP BY developer_id, developer_name
                ORDER BY last_seen DESC
               )Zdays
last_monthr-   r
   zFrom database records (z activities)Ndatabase)r1   r2   r$   r%   r.   r/   r5   	last_seenactivity_countsourcer7   z!Error discovering from database: )r   
sqlalchemyr`   r   r@   r   Zexecuter   developer_iddeveloper_namere   rd   rA   r   r   error)r   r`   Zqueryrb   rM   Zdb_developersrowr"   r   r   r   discover_from_database   s4    


z)DeveloperDiscovery.discover_from_databaseT)scan_network
scan_localscan_databaser   c                 C   s  g }|r:t d |  }|| t dt| d |rpt d |  }|| t dt| d |rt d |  }|dd D ]F}t d	| d
 | |}	||	 t dt|	 d| d qi }
|D ]x}|dp|d  d|d  }||
kr||
|< q|
| }|ddkr|ds|dd|d< |d|d< qt	|

 | _t dt| j  | jS )z2Discover all available developers from all sourcesz,Discovering local ActivityWatch instances...zFound z local instancesz'Discovering developers from database...z developers in databasez/Scanning network for ActivityWatch instances...N   zScanning network z.x...z instances on z.xr/   r$   r'   r%   rf   rc   re   r   rd   z$Total unique developers discovered: )r   rE   r_   extendr?   rl   r#   rZ   r:   r   valuesr   )r   rm   rn   ro   Zall_developersZ
local_devsZdb_devsr!   ZnetworkZnetwork_devsZunique_developersdevkeyexistingr   r   r   discover_all_developers   s>    






"

z*DeveloperDiscovery.discover_all_developers)
developersr   c              	      s6    fdd}t dd}t|||}W 5 Q R X |S )z(Refresh online status for all developersc                    s`   |  ddkr(|  ddkr(d| d< | S  | d | d }|rDdnd	| d< t  | d
< | S )Nrf   rc   r$   r-   Zdatabase_onlyr7   r%   r0   Zofflinelast_checked)r:   rF   r   r@   rA   )rs   rM   rN   r   r   check_status   s    zADeveloperDiscovery.refresh_developer_status.<locals>.check_status   rR   )r   r   map)r   rw   ry   rU   Zupdated_developersr   rN   r   refresh_developer_status   s    
z+DeveloperDiscovery.refresh_developer_status)N)N)TTT)__name__
__module____qualname____doc__r   r   strr#   intr   r   rF   r=   rZ   r_   rl   boolrv   r|   r   r   r   r   r	      s   
"
$/   1r	   )r   )r	   c                   @   s   e Zd ZdZedddZedddZeddd	Ze	e
e
ee d
ddZe
e
ee dddZe	e	dddZee	dddZe	e	dddZdS )DynamicActivityWatchClientzFActivityWatch client that works with dynamically discovered developers)developer_infoc                 C   sN   || _ |d | _|d | _|d | _d| j d| j | _|d| j| _d S )Nr1   r$   r%   r&   r'   r/   )r   rh   r$   r%   base_urlr:   r/   )r   r   r   r   r   r     s    


z#DynamicActivityWatchClient.__init__r   c                 C   s6   z"t j| j ddd}|jdkW S    Y dS X dS )z'Test connection to ActivityWatch serverr(      r)   r*   FN)r9   r:   r   r;   )r   rD   r   r   r   test_connection  s
    z*DynamicActivityWatchClient.test_connectionc              
   C   sb   z$t | j d}|  | W S  tk
r\ } ztd|  i  W Y S d}~X Y nX dS )zGet all available buckets/api/0/buckets/zError getting buckets: N)r9   r:   r   raise_for_statusr<   r   print)r   rD   r"   r   r   r   get_buckets  s    
z&DynamicActivityWatchClient.get_buckets)	bucket_id
start_timeend_timer   c              
   C   s   z@|  |  d}tj| j d| d|d}|  | W S  tk
r~ } z td| d|  g  W Y S d}~X Y nX dS )z!Get events from a specific bucket)startendr   z/events)paramszError getting events from r8   N)rA   r9   r:   r   r   r<   r   r   )r   r   r   r   r   rD   r"   r   r   r   
get_events(  s    
z%DynamicActivityWatchClient.get_events)r   r   r   c                 C   s(  z|   }dd | D }g }|D ]}| |||}|D ]}|di }	|| j| jd | jd |	d|	dd|	d	d
|dd|dd
| |	dd
| |	|	dd
| 	|	d	d
|| j
d q:q$t|dd ddW S  tk
r" }
 ztd|
  g  W Y S d}
~
X Y nX dS )zGet processed activity datac                 S   s   g | ]}d |  kr|qS )Zwindowlower)rT   r2   r   r   r   
<listcomp>:  s      z@DynamicActivityWatchClient.get_activity_data.<locals>.<listcomp>datar2   r.   appZapplicationZUnknowntitle durationr   	timestamprC   )rh   ri   developer_hostnameapplication_namewindow_titler   r   categorydetailed_activityrC   	file_pathrH   r/   c                 S   s   | d S )Nr   r   )xr   r   r   <lambda>R      z>DynamicActivityWatchClient.get_activity_data.<locals>.<lambda>T)rt   reversezError getting activity data: N)r   r>   r   r:   r   rh   r   _categorize_activity_get_detailed_activity_extract_file_pathr/   sortedr   r   )r   r   r   r3   Zwindow_bucketsZ
activitiesrH   ZeventsZeventr   r"   r   r   r   get_activity_data6  s6    



z,DynamicActivityWatchClient.get_activity_data)app_namer   c                    s^   |   t fdddD r"dS t fdddD r<dS t fddd	D rVd
S dS dS )z-Categorize activity based on application namec                 3   s   | ]}| kV  qd S rL   r   )rT   ZideZ	app_lowerr   r   	<genexpr>\  s     zBDynamicActivityWatchClient._categorize_activity.<locals>.<genexpr>)cursorcodeZvimZsublimeZatomZintellijZpycharmZDevelopmentc                 3   s   | ]}| kV  qd S rL   r   rT   Zbrowserr   r   r   r   ^  s     )chromefirefoxsafariZedgezWeb Browsingc                 3   s   | ]}| kV  qd S rL   r   )rT   Zcommr   r   r   r   `  s     )ZslackZdiscordZteamsZzoomZCommunicationZOtherN)r   any)r   r   r   r   r   r   X  s    z/DynamicActivityWatchClient._categorize_activity)r   r   c                    s   | dd| dd d  ks0d  krRtfdddD rd	 S n.t fd
ddD r| drd|d  S S )z!Get detailed activity descriptionr   r   r   r   r   c                 3   s   | ]}|   kV  qd S rL   r   rT   extr   r   r   r   k  s     zDDynamicActivityWatchClient._get_detailed_activity.<locals>.<genexpr>).py.js.html.css.jsonzCoding: c                 3   s   | ]}|   kV  qd S rL   r   r   )r   r   r   r   m  s     )r   r   r   rC   z
Browsing: )r:   r   r   )r   r   r   )r   r   r   r   e  s    
z1DynamicActivityWatchClient._get_detailed_activity)r   r   c                    s8   t  fdddD r4 d}t|dkr4|d S dS )z0Extract file path from window title if availablec                 3   s   | ]}|   kV  qd S rL   r   r   r   r   r   r   u  s     z@DynamicActivityWatchClient._extract_file_path.<locals>.<genexpr>)r   r   r   r   r   z.mdz - rG   r   r   )r   r   r?   )r   r   rI   r   r   r   r   s  s
    
z-DynamicActivityWatchClient._extract_file_pathN)r}   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r     s   
"r   )ColumnIntegerStringDateTimeFloatTextBoolean)declarative_base)funcc                   @   s   e Zd ZdZeedddZeeddZeedddZ	eedddZ
eedZeedddZeedddZeeZeeZeedddZeeZeeZeedZeedZeeZeee dZeee e d	Zd
S )ActivityRecordZactivity_recordsT)primary_keyindex)r   rP   d   i  defaultr   ZonupdateN)r}   r~   r   __tablename__r   r   r1   Zuser_idr   rh   ri   r   r/   r   r   r   r   r   r   r   rC   r   rH   r   Zactivity_timestampr   r@   
created_at
updated_atr   r   r   r   r     s$   r   c                   @   s   e Zd ZdZdZeedddZeedZeedZ	ee
ZeedZeedZeeZeedZee
ddZee
ddZeedd	dZeeZeeZeedZeee dZeeddZeee dZeee e d
ZdS )DiscoveredDeveloperz;Cache discovered developers to avoid repeated network scansr   rP   T)r   rQ   r   r   r-   r   N)r}   r~   r   r   r   r   r   r1   r2   r$   r   r%   r.   r/   r   r5   r,   r4   re   r7   r   rd   rx   rf   r   r@   r6   r   Z	is_activer   r   r   r   r   r   r     s(   r   )%r9   r^   	threadingr   r   typingr   r   r   r<   Zloggingconcurrent.futuresr   r   Zpsutilr   Z	getLoggerr}   r   r	   Zdeveloper_discoveryr   rg   r   r   r   r   r   r   r   Zsqlalchemy.ext.declarativer   Zsqlalchemy.sqlr   ZBaser   r   r   r   r   r   <module>   s.   
 wr$