o
    l>j]t                     @  s$  d Z ddlm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mZ ddlmZmZmZ ddlmZmZ ddlmZmZmZmZ dd	lmZmZ dd
lmZmZmZ ddlm Z m!Z! ddl"m#Z#m$Z$m%Z% dZ&h dZ'h dZ(e)dZ*e+e,Z-edddG dd dZ.eddG dd dZ/eddG dd dZ0de	j1ddd%d&Z2dd'd(Z3dd+d,Z4dd/d0Z5dd=d>Z6dd@dAZ7ddDdEZ8ddKdLZ9ddPdQZ:ddYdZZ;dd[d\Z<dd^d_Z=ddbdcZ>ddedfZ?ddjdkZ@ddodpZAddqdrZBddtduZCddxdyZDdd~dZEdS )z9State-backed sync pipeline for the modern Photos service.    )annotationsN)	dataclassfield)datetime	timedeltatimezone)PathPurePosixPath)AnyCallableIterableIterator   )(legacy_shared_stream_unsupported_message(unsupported_shared_library_album_message)apply_align_raw_policyset_exif_datetime_if_missingwrite_xmp_sidecar)PhotoResourcePhotosServiceException)PhotoSyncStateSyncedPhotoResourcecreate_photo_sync_statenone>   thumbmediumoriginalz[\x00-\x1f/\\:]+T)slotsfrozenc                   @  s  e Zd ZU dZded< dZded< dZded	< d
Zded< dZded< dZ	ded< e
Zded< dZded< dZded< dZded< dZded< dZded< dZded< dZded< dZded< dZded< dZded< dZded< d,d!d"Zd-d$d%Zd.d&d'Zd/d(d)Zd/d*d+ZdS )0PhotoSyncOptionszDOptions controlling a photos sync target and materialization policy.r   	directoryNzPath | None	state_dirrootstrlibrary tuple[str, ...]albumsr   sizelive_photo_sizefolder_structure
int | Nonerecentuntil_foundFboolskip_videosskip_live_photosas-is	align_rawxmp_sidecarset_exif_datetimekeep_icloud_recent_daysonly_print_filenamesdry_runauto_deletereturnc                 C  s   t tdd | jD S )z&Return a stable album selection tuple.c                 s  s    | ]}|r|V  qd S Nr%   ).0albumr%   r%   [/home/thesage/.local/lib/python3.10/site-packages/pyicloud/services/photos_cloudkit/sync.py	<genexpr>>   s    z5PhotoSyncOptions.normalized_albums.<locals>.<genexpr>)tuplesortedr'   selfr%   r%   r=   normalized_albums;   s   z"PhotoSyncOptions.normalized_albumsdict[str, Any]c                 C  s8   | j |  t| j | j| j| j| j| j	| j
| jd
S )z2Return the persisted sync-target identity payload.)
r$   r'   r    r(   r)   r*   r,   r/   r0   r2   )r$   rC   r#   r    resolver(   r)   r*   r,   r/   r0   r2   rA   r%   r%   r=   target_payload@   s   zPhotoSyncOptions.target_payloadc                 C  s|   t j|  ddd}t|d dd }d}| jr1dd	d
 | 	 D }|dd p0d}t
| j d| d| S )z7Return a stable identifier for the current sync target.T),:)	sort_keys
separatorszutf-8N   all-c                 s  s    | ]}t |V  qd S r:   )_sanitize_name)r;   namer%   r%   r=   r>   Y   s    
z.PhotoSyncOptions.target_key.<locals>.<genexpr>0   r'   )jsondumpsrF   hashlibsha1encode	hexdigestr'   joinrC   rN   r$   )rB   payloaddigestalbum_labelr%   r%   r=   
target_keyP   s   

zPhotoSyncOptions.target_keyc                 C  s   | j p| jd S )z7Return the directory where sync state should be stored.z.pyicloud-state)r!   r    rA   r%   r%   r=   
state_root_   s   zPhotoSyncOptions.state_rootc                 C  s   |   |   d S )z,Return the SQLite path for this sync target.z.sqlite3)r\   r[   rA   r%   r%   r=   
state_pathd   s   zPhotoSyncOptions.state_path)r9   r&   r9   rD   )r9   r#   )r9   r   )__name__
__module____qualname____doc____annotations__r!   r$   r'   r(   r)   DEFAULT_FOLDER_STRUCTUREr*   r,   r-   r/   r0   r2   r3   r4   r5   r6   r7   r8   rC   rF   r[   r\   r]   r%   r%   r%   r=   r   $   s2   
 



r   )r   c                   @  sH   e Zd ZU dZded< ded< ded< ded< dZded	< dddZdS )PhotoSyncItemzAOne file-level action performed or considered by the sync engine.r#   asset_idresource_keypathactionN
str | Nonereasonr9   rD   c                 C  s   | j | j| j| j| jdS )z$Return a JSON-friendly item payload.rf   rg   rh   ri   rk   rl   rA   r%   r%   r=   as_dictt   s   zPhotoSyncItem.as_dictr^   )r_   r`   ra   rb   rc   rk   rm   r%   r%   r%   r=   re   j   s   
 re   c                   @  s   e Zd ZU dZded< ded< ded< ded< dZd	ed
< dZded< dZded< dZded< dZ	ded< dZ
ded< eedZded< dddZdS )PhotoSyncResultzSummary of one sync run.r#   r    r]   r$   z	list[str]r'   Nrj   sync_cursorFr.   short_circuitedr   intdownloaded_countskipped_countdeleted_countlisted_count)default_factoryzlist[PhotoSyncItem]itemsr9   rD   c                 C  s<   | j | j| j| j| j| j| j| j| j| j	dd | j
D dS )z'Return a JSON-friendly summary payload.c                 S  s   g | ]}|  qS r%   )rm   )r;   itemr%   r%   r=   
<listcomp>       z+PhotoSyncResult.as_dict.<locals>.<listcomp>r    r]   r$   r'   ro   rp   rr   rs   rt   ru   rw   r{   rA   r%   r%   r=   rm      s   zPhotoSyncResult.as_dictr^   )r_   r`   ra   rb   rc   ro   rp   rr   rs   rt   ru   r   listrw   rm   r%   r%   r%   r=   rn      s   
 rn   )
iterationssleep_fnservicer
   optionsinterval_secondsrq   r}   r+   r~   Callable[[float], None]r9   Iterator[PhotoSyncResult]c                c  s    |dk r	t d|dur|dk rt dd}|du s||k rAt| |V  |d7 }|dur3||kr3dS || |du s||k sdS dS )z3Yield repeated sync runs for the given sync target.r   z%--interval must be at least 1 second.Nz --iterations must be at least 1.r   )r   run_photo_sync)r   r   r   r}   r~   	completedr%   r%   r=   watch_photo_sync   s   
r   c           "      C  sF  |j tvrtd|j  d|jtvrtd|j d|jdvr*td|j d|jr6|jdur6td|jdurD|jd	k rDtd
|jdurR|jd	k rRtd|j	dur`|j	dk r`td|j	durn|jdurntd|j
s||js||jjddd tt|jt| |jt| d}t| |j
s|jo|   d}|}t| |j}t|| }||_d}t| }dd |D }	t||j||j|j
|j|j|j|j	d	rd|_|W  d   S t }
t }d}d}|jdurt t!j"t#|jd }t  $ }t%| ||D ]}t&|dd}|dur%|du s#||k r%qt'||}|s/qd}d}g }|D ]F\}}t(t)|||j*|j+|||	d}|,| |
,|j+|f |-| zt.|j|}W n8 ty } z+t/0d||j| |j1-t2|j+||ddd | j3d	7  _3d}d}d}W Y d}~q7d}~ww |4|j+|}t5||||r|j1-t2|j+||ddd | j3d	7  _3d}t6|||||d  |d	7 }|jdur||jkr nq7d}|jrd!nd"}|j
rd!}|js|j
r|j1-t2|j+||||j
rd#nd$d | j7d	7  _7d}q7|j8|d%}|du r>d}d}|j1-t2|j+||dd&d | j3d	7  _3q7t9|| t6|||||d  t t!j": }|;t<|j+|||j t=|d'd|d( |j1-t2|j+||d"d) | j>d	7  _>d}q7t?|||||d*r|@ }d}|r|j1-t2|j+d+|r|d n|jAd,d-d | jBd	7  _B|jdur||jkr nq|jrh|jsh|j
sh|D ]}|jC|jDf} | |
v rܐqz	t.|j|jE}!W n; ty  } z.t/0d.|jE| |F|jC|jD |j1-t2|jC|jD|jEddd | j3d	7  _3W Y d}~qd}~ww |! rHz|!G  W n tHyG } zt/0d/|!| W Y d}~qd}~ww |F|jC|jD |j1-t2|jC|jD|jEd,d) | jBd	7  _Bq|r|js|j
s|I| W d   |S W d   |S W d   |S W d   |S 1 sw   Y  |S )0zCSynchronize selected photo resources into a local output directory.zUnsupported photo size 'z('. Choose from: original, medium, thumb.zUnsupported live photo size '>   r   alternativer1   zUnsupported RAW alignment 'z-'. Choose from: as-is, original, alternative.Nz4--auto-delete cannot be combined with --until-found.r   z!--until-found must be at least 1.z --recent must be at least 1 day.r   z---keep-icloud-recent-days must be at least 0.z@--keep-icloud-recent-days cannot be combined with --until-found.Tparentsexist_ok)r    r]   r$   r'   )	ephemeralc                 S  s   i | ]
}|j |j|jfqS r%   )relative_pathrf   rg   r;   entryr%   r%   r=   
<dictcomp>   s    z"run_photo_sync.<locals>.<dictcomp>)	stater    current_cursorr8   r7   r6   r3   r4   r5   )days
added_date
asset_dateF)	candidaterf   rg   reserved_pathstracked_pathsz6Skipping resource with unsafe path '%s' under '%s': %sskippedzunsafe-pathrl   zalready-current)assetresourcerg   target_pathr   listed
downloadedzdry-runz
print-only)versionzmissing-download-datachecksum)rf   rg   r   r(   r   downloaded_at)rf   rg   rh   ri   )r   r   asset_ready_for_deleteasset_confirmed_local	now_localremotedeletedzkeep-icloud-recent-daysz/Ignoring unsafe stale local photo path '%s': %sz+Failed to remove stale local photo '%s': %s)Jr(   PRIMARY_SYNC_VERSIONSr   r)   LIVE_PHOTO_SYNC_VERSIONSr2   r8   r-   r,   r5   r7   r6   r    mkdirrn   r#   r]   r$   r|   rC   r   exists_resolve_library_sync_cursorro   iter_resources_can_short_circuitr3   r4   rp   setr   nowr   utcr   
astimezone_iter_sync_assets_asset_datetime_select_resources_unique_relative_path_render_relative_pathr*   idaddappend_safe_target_path_LOGGERwarningrw   re   rs   get_resource_is_current_file_apply_local_metadataru   download_atomic_write_bytes	isoformatupsert_resourcer   getattrrr   _should_delete_remote_assetdeletefilenamert   rf   rg   r   delete_resourceunlinkOSErrorset_sync_cursor)"r   r   resultstate_backendr   selected_libraryr   sync_completetracked_resourcesr   current_entriesr   consecutive_seencutoffr   r   added_at	resourcesr   r   asset_pathsrg   r   r   r   excmanifestri   datar   r   stalekey
stale_pathr%   r%   r=   r      sr  










	
			
	


	
	
 
 { 
 { 
 { 
 {  {r   library_keyr#   c                 C  sh   t | di }t|tstd||}|d u r td| d|dks,t |dd dkr2tt||S )N	librariesz2Photos service does not expose syncable libraries.zNo photo library matched ''.sharedscopezshared-stream)r   
isinstancedictr   getr   )r   r   r   r$   r%   r%   r=   r     s   

r   r$   rj   c                 C  s(   t | dr	|  S t |dr| S d S )Nro   )hasattrro   )r$   r   r%   r%   r=   r     s
   

r   r   r   r    r   r   r8   r.   r7   r6   r3   r4   r5   c        	      	   C  s   |s|s|s|s|s|d urdS |d u s|   |krdS |  dkr$dS |  D ]<}	zt||	j}
W n ty=   Y  dS w |
 sE dS |	jd urdz|
 j	|	jkrWW  dS W q( t
yc   Y  dS w q(dS )NFr   T)get_sync_cursorresource_countr   r   r   r   r   r(   statst_sizer   )r   r    r   r8   r7   r6   r3   r4   r5   r   rh   r%   r%   r=   r     sD   
r   Iterator[Any]c           
      c  sT   t  }| }|ret|dt| dd }|d u st|ds&td|j d|D ]:}||}|d u rKt|dd dkrCtt|j|td| dt|d	D ]}|j|v rXqP|	|j |V  qPq(d S t|d
d d ur||j
d usw|jd ur|| }	nt|dd d ur|j}	n	td|j dt|	d	D ]}|j|v rq|	|j |V  qd S )Nr'   findzPhoto library 'z$' does not support album-based sync.r   zshared-libraryzNo album named 'z' was found.photosrecently_addedrL   z'' does not expose a default asset feed.)r   rC   r   r   r   r$   r   r   r   r   r,   r-   r   rL   )
r   r$   r   seenalbum_namesalbum_container
album_namer<   r   sourcer%   r%   r=   r   *  sV   




r   r   list[tuple[str, PhotoResource]]c                 C  s   t t| di |j}| jdkr'|jrg S t||jdddg}|d u r$g S |gS t| ddr2|jr2g S g }t||jdddg}|d urG|| t| ddrv|jsvdd	d
d|j	 }t||dd	d
g}|d urv|d dd |D vrv|| |S )Nr   movier   r   r   is_live_photoForiginal_videomedium_videothumb_video)r   r   r   r   c                 S  s   h | ]}|d  qS )r   r%   r   r%   r%   r=   	<setcomp>}  rz   z$_select_resources.<locals>.<setcomp>)
r   r   r2   	item_typer/   _resolve_resourcer(   r0   r   r)   )r   r   r   primaryselectedlive_keyliver%   r%   r=   r   \  s>   


r   r   dict[str, PhotoResource]
candidatesIterable[str] tuple[str, PhotoResource] | Nonec                 C  s>   |D ]}|  |}|d ur|jrt|dd p||f  S qd S )Nr   )r   urlr   )r   r  r   r   r%   r%   r=   r     s   
r   r   r   r*   c              
   C  s   t |}|dkr
|S t| dptdtj}zd|v r!||}n||}W n ty< } z	t	d| d|d }~ww t
|}|sE|S t|| }| S )Nr   r   r   {z!Invalid folder structure format 'r   )_safe_resource_filenamer   r   fromtimestampr   r   formatstrftime	Exceptionr   _safe_relative_folderr	   as_posix)r   r   r*   r   r   folderr   relativer%   r%   r=   r     s0   

r   r   rf   rg   r   set[str]r   dict[str, tuple[str, str]]c                 C  s   | | }| |vr|d u s|||fkr| S t| }|j}|j}|j}	|d d }
d}	 |dkr4d|
 nd|
 d| }|	| | |   }| |}||vr]|d u s[|||fkr]|S |d7 }q+)N   r   T_)r   r	   stemsuffixparentr  )r   rf   rg   r   r   ownerrh   r  r  r    discriminatorindexsuffix_bits	next_pathr%   r%   r=   r     s&   
"
r   c                 C  s2   t t| ddpd}t|ddj}t|ddS )Nr    photo\/fallback)r#   r   r	   replacerO   _sanitize_path_component)r   r   r%   r%   r=   r    s   r  r  c                 C  sL   g }t | ddjD ]}|dv rq|t|dd q|s dS t |  S )Nr  r  >   r  .r  ..r  r  r  )r	   r!  partsr   r"  r  )r  r%  partr%   r%   r=   r    s   r  valuer   c                C  s    t d|  }|dv r|S |S )NrM   >   r  r#  r$  )UNSAFE_PATH_COMPONENT_CHARSsubstrip)r'  r   	sanitizedr%   r%   r=   r"    s   r"  r   c              
   C  sV   | j dd}|| j dd}z|| W |S  ty* } ztd| |d }~ww )NF)strictz0Refusing to access path outside sync directory: )rE   relative_to
ValueErrorr   )r    r   basetargetr   r%   r%   r=   r     s   r   rh   r   SyncedPhotoResource | Nonec                 C  sl   |d u rdS |j |krdS |  sdS |jd ur"|  j|jkr"dS t|dd }|r4|jr4||jkr4dS dS )NFr   T)r   r   r(   r   r   r   r   )rh   r   r   r   r   r%   r%   r=   r     s   
r   r   bytesNonec              	   C  s   | j jddd tjd| j d\}}z<t|d}|| |  t|	  W d    n1 s4w   Y  t
||  W tj|rMt| d S d S tj|r[t| w w )NTr   z.pyicloud-sync-)prefixdirwb)r  r   tempfilemkstemposfdopenwriteflushfsyncfilenor!  rh   r   r   )rh   r   fd	temp_pathhandler%   r%   r=   r     s   
r   c                 C  s    t dd| d }|pdS )Nz[^A-Za-z0-9._-]+rM   r0  )rer)  r*  lower)r'  r+  r%   r%   r=   rN   
  s   rN   r   c                 C  sf   |j s|jrd S t| d}|jr|d urt|| |jr/|ds1t|t| dd |j d d S d S d S )Nr   _video_asset_record)rh   asset_recordr7   )	r7   r6   r   r4   r   r3   endswithr   r   )r   r   rg   r   r   taken_atr%   r%   r=   r     s   



r   attrsdatetime | Nonec                 G  sH   |D ]}t | |d }t|tsq|jd u r|jtjd  S |  S d S )N)tzinfo)r   r   r   rK  r!  r   r   )r   rI  attrr'  r%   r%   r=   r   $  s   

r   r   r   r   r   c                 C  s\   |j d u rdS |js|jrdS |r|sdS t| d}|d u r dS |||j j}||j kS )NFr   )r5   r6   r7   r   r   rK  r   )r   r   r   r   r   r   age_daysr%   r%   r=   r   /  s   


r   )r   r
   r   r   r   rq   r}   r+   r~   r   r9   r   )r   r
   r   r   r9   rn   )r   r
   r   r#   )r$   r
   r   r
   r9   rj   )r   r   r    r   r   rj   r8   r.   r7   r.   r6   r.   r3   r.   r4   r.   r5   r+   r9   r.   )r   r
   r$   r
   r   r   r9   r   )r   r
   r   r   r9   r   )r   r   r  r  r9   r  )r   r
   r   r   r*   r#   r9   r#   )r   r#   rf   r#   rg   r#   r   r  r   r  r9   r#   )r   r   r9   r#   )r  r#   r9   r#   )r'  r#   r   r#   r9   r#   )r    r   r   r#   r9   r   )
rh   r   r   r1  r   r   r   r#   r9   r.   )rh   r   r   r2  r9   r3  )r'  r#   r9   r#   )r   r
   r   r   rg   r#   r   r   r   r   r9   r3  )r   r
   rI  r#   r9   rJ  )r   r
   r   r   r   r.   r   r.   r   r   r9   r.   )Frb   
__future__r   rS   rQ   loggingr9  rB  r7  timedataclassesr   r   r   r   r   pathlibr   r	   typingr
   r   r   r   	constantsr   r   materializer   r   r   modelsr   r   r   r   r   r   rd   r   r   compiler(  	getLoggerr_   r   r   re   rn   sleepr   r   r   r   r   r   r   r   r   r   r  r  r"  r   r   r   rN   r   r   r   r%   r%   r%   r=   <module>   sh    


E&
  
1


)
2
&










