
    l8jN                       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mZmZ ddl	m
Z
 dZdZ e
e                                          j        j        dz  Zd	hZh d
ZdZdZdPdZdQdZdRdZdSdZdTdZdUdVdZdWd ZdXd$ZdYd&ZdZd(Zd[d-Zd\d0Z d]d6Z!d^d9Z"d:Z#d_d<Z$d`d=Z%dad>Z&dbdDZ'dcdGZ(	 	 	 dddedKZ)dfdMZ*dgdNZ+e,dOk    r e- e+                      dS )hu  
Генератор манифеста клиента WoW (nested: client + patches).

Использование:
  python generate-client-manifest.py <client-dir> [--version VER] [--base-url URL] [--output FILE]
  python generate-client-manifest.py <patch-dir> --add-patch PATCH_ID [--patch-url URL]

Режим --add-patch (по умолчанию):
  <patch-dir> — каталог ТОЛЬКО с файлами патча (как на CDN).
  Сравниваются только отсканированные файлы; удаления не вычисляются.

  Для diff всего клиента с удалениями — флаг --scan-full-client и путь к полной установке.

Примеры:
  python generate-client-manifest.py /path/to/WoW
  python generate-client-manifest.py /path/to/patches/patch-001 --add-patch patch-001
  # Пути patch-001/Data/... автоматически станут Data/... (префикс = patch_id)

  python generate-client-manifest.py /path/to/patches --add-patch patch-001
  # Если все файлы под одной папкой patch-001/, префикс тоже срежется
    )annotationsN)datetimetimezone)Pathz3.3.5.12340zhttps://dl.neix.ru/client/zclient-manifest.jsonzWow.exe.ppdb>   WTFLogsCachesoundErrorsGLCache)zInterface/AddOnszData/ruRU/Documentationi   returnstrc                     t          j        t          j                                                                      dd          S )N+00:00Z)r   nowr   utc	isoformatreplace     ./var/www/downloads/generate-client-manifest.pyutc_nowr   ,   s0    <%%//1199(CHHHr   versionintc                    	 t          |                     dd          d                   S # t          t          f$ r Y dS w xY w)N.   i40  )r   rsplit
ValueError
IndexError)r   s    r   parse_buildr#   0   sO    7>>#q))"-...
#   uus   (+ A A pathr   c                *    |                                  S N)as_posixr$   s    r   normalize_rel_pathr)   7   s    ==??r   rel_pathdir_nameboolc                Z     |t           v rdS t           fdt          D                       S )NTc              3  T   K   | ]"}|k    p                     d |z             V  #dS /N)endswith).0patternr*   s     r   	<genexpr>z"should_skip_dir.<locals>.<genexpr>>   sS         	G?x00w??     r   )	SKIP_DIRSanySKIP_PATH_PATTERNS)r*   r+   s   ` r   should_skip_dirr8   ;   sK    9t    )     r   	file_pathc                   t          j                    }|                     d          5 }	 |                    t                    }|sn|                    |           3	 d d d            n# 1 swxY w Y   |                                S )Nrb)hashlibsha256openread
CHUNK_SIZEupdate	hexdigest)r9   digeststreamchunks       r   compute_sha256rF   D   s    ^F			 !	!KK
++E MM%   		! 	! ! ! ! ! ! ! ! ! ! ! ! ! ! ! s   5A++A/2A/
client_dir
list[dict]c           
        	 |                                  st          d|            g }d}t          |                     d                    D ]}|                                s|                    |           }t          |          	t          d |j        D                       r[d|j        v re|j	        t          v rtt          d |j        d d         D                       rt          	fdt          D                       r	 |                                }t          |          }|                    	d	| |j        t!          j        |j        t&          j        
                                                              dd          d           |dz  }|r ||	           `# t.          $ r,}t1          d	 d| t2          j                   Y d }~d }~ww xY w|                    d            |S )Nu"   Каталог не найден: r   *c              3  @   K   | ]}|                     d           V  dS )r   N
startswithr2   parts     r   r4   zscan_files.<locals>.<genexpr>]   s.      ::ts##::::::r   z.launcher_downloadc              3  (   K   | ]}|t           v V  d S r&   )r5   rN   s     r   r4   zscan_files.<locals>.<genexpr>d   s'      <<Tty <<<<<<r   r   c              3  T   K   | ]"}|k    p                     |d z             V  #dS r/   rL   )r2   r3   rel_strs     r   r4   zscan_files.<locals>.<genexpr>f   sS       
 
 wC'"4"4Ws]"C"C
 
 
 
 
 
r   zsha256:)tzr   r   )r$   hashsize
updated_atr   u     ⚠ Ошибка:     — filec                    | d         S Nr$   r   items    r   <lambda>zscan_files.<locals>.<lambda>   s
    V r   key)is_dirFileNotFoundErrorsortedrglobis_filerelative_tor)   r6   partsname
SKIP_FILESr7   statrF   appendst_sizer   fromtimestampst_mtimer   r   r   r   OSErrorprintsysstderrsort)
rG   on_progressfilescountentryrelstats	file_hashexcrR   s
            @r   
scan_filesr|   O   sh    S QZ Q QRRREE
((--.. )O )O}} 	
++$S))::	::::: 	39,,:##<<SYss^<<<<< 	 
 
 
 
-
 
 
 
 
 	 	OJJLLE&u--ILL#1i11!M"*"88<# # # Y[[WXs++	 	   QJE ,E7+++ 	O 	O 	O<<<s<<3:NNNNNNNNN	O 
JJ,,J---Ls   B!F..
G$8!GG$dict | Nonec                    |                                  sd S |                     dd          5 }t          j        |          cd d d            S # 1 swxY w Y   d S )Nrutf-8encoding)re   r>   jsonload)r$   rD   s     r   load_manifestr      s    <<>> t	3	)	) !Vy  ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !s   AAAmanifestdictNonec                    | j                             dd           |                     dd          5 }t          j        ||dd           |                    d	           d d d            d S # 1 swxY w Y   d S )
NT)parentsexist_okwr   r   F   )ensure_asciiindent
)parentmkdirr>   r   dumpwrite)r$   r   rD   s      r   save_manifestr      s    KdT222	3	)	) V	(FqAAAAT                 s   .A//A36A3clientc                ~    t          d | d         D                       }|| d<   t          | d                   | d<   d S )Nc              3  &   K   | ]}|d          V  dS rU   Nr   r2   r]   s     r   r4   z#summarize_client.<locals>.<genexpr>   s&      >>dT&\>>>>>>r   ru   
total_sizetotal_files)sumlen)r   r   s     r   summarize_clientr      sH    >>fWo>>>>>J%F<w00F=r   callablec                     dd} | S )	Nrv   r   r*   r   r   r   c                F    t          d|  d|d d          dd           d S )Nu&     Обработано файлов: rW   <    T)endflush)rp   )rv   r*   s     r   r   zprogress_writer.<locals>.write   s8    SSSHSbSMSSY[cghhhhhhr   )rv   r   r*   r   r   r   r   )r   s    r   progress_writerr      s!    i i i i Lr   base_urloutputmirrorslist[str] | Nonec                "   t          d           t          d|             t          d|            t          d| d           t          j        t          j                  }t          | t                                }t                       |                    d          dz   |d}t          |           |t          |          t                      t                      d	|g d
}|r||d<   t          ||           t          j        t          j                  |z
                                  }	t          d|            t          d|d                     t          d|d         dz  dd           t          d|	dd           d S )NuI   
📦 Генерация полного манифеста клиента   📂 Каталог: u   📋 Версия:    🌐 Base URL: r   rt   r0   )r   ru   )r   build
created_atrV   )metadatar   patchesr   u(   
✅ Манифест сохранён: u   📊 Файлов: r   u   📦 Размер: r   i   @.2fu    ГБ   ⏱ Время:    с)rp   r   r   r   r   r|   r   rstripr   r#   r   r   total_seconds)
rG   r   r   r   r   startedru   r   r   elapseds
             r   generate_full_manifestr      s    

VWWW	
.*
.
.///	
)
)
)***	
(H
(
(
()))l8<((Gz/@/@AAAE	GGG OOC((3. F V  ))!))!))	
 
 	 	H  &%&(###|HL))G3BBDDG	
>f
>
>???	
7} 5
7
7888	
M| 4	 B
M
M
M
MNNN	
,W
,
,
,
,-----r   patch_idset[str]c                   | h}|                      d          r.|                    d| t          d          d         z              nB|                      d          r-|                    d| t          d          d         z              |S )uE   patch-001 ↔ patches-001 и имя каталога на диске.zpatch-zpatches-N)rM   addr   )r   variantss     r   _patch_id_variantsr      s    zH8$$ =Z(3x==??";;<<<<			Z	(	( =XZ):): ;;<<<Or   ru   scan_dirstrip_prefix
str | None
auto_stripc                0   | r|s|r|                     d          ndS |r|                     d          S t          |          }|                    |j                   t	                      }| D ]N}|d                             dd          }|                    t          |          dk    r|d         nd           Ot          |          dk    r|                                }	|	r|	|v r|	S |D ]&}
|
dz   t          fd| D                       r|
c S 'dS )uy   Префикс каталога патча в path, который нужно убрать (уже есть в base_url).r0   Nr$   r   r   r   c              3  N   K   | ]}|d                                         V   dS )r$   NrL   )r2   r]   needles     r   r4   z+detect_patch_path_prefix.<locals>.<genexpr>   s5      AA4tF|&&v..AAAAAAr   )	stripr   r   rh   setsplitr   popall)ru   r   r   r   r   r   
top_levelsr]   rg   segment	candidater   s              @r   detect_patch_path_prefixr      s]     A
 A*6@|!!#&&&D@ '!!#&&&!(++HLL 55J ; ;V""3**3u::>>uQxxr::::
:!.."" 	w(**N   	SAAAA5AAAAA 		 4r   prefixtuple[list[dict], int]c                .   |s| dfS |                     d          dz   }g }d}| D ]V}|d         }|                    |          r|t          |          d         }|dz  }|                    i |d|i           W|                    d            ||fS )ui   Убирает patch-001/ из path → пути относительно корня игры (Data/...).r   r0   r$   Nr   c                    | d         S r[   r   )xs    r   r^   z#strip_path_prefix.<locals>.<lambda>  s
    &	 r   r_   )r   rM   r   rk   rs   )ru   r   r   strippedrv   r]   r$   s          r   strip_path_prefixr      s     ax]]3#%FHE 0 0F|??6"" 	F&DQJE.4...////MM))M***U?r   )files_addedfiles_updatedru   dict[str, dict]c                :   i }|                      di                                dg           D ]}|||d         <   |                      dg           D ]I}t          D ]?}|                     |g           D ]&}t          |t                    rd|v r|||d         <   '@J|S )Nr   ru   r$   r   )getPATCH_FILE_KEYS
isinstancer   )r   knownr]   patchr`   s        r   collect_known_filesr     s    EXr**..w;; # #"d6li,, / /" 	/ 	/C		#r** / /dD)) /fnn*.E$v,'/	/
 Lr   c                $   t                      }|                     dg           D ]j}t          D ]`}|                    |g           D ]G}t          |t                    r0|                    d          r|                    |d                    Hak|S )Nr   r$   )r   r   r   r   r   r   )r   pathsr   r`   r]   s        r   patch_file_pathsr   "  s    eeEi,, , ," 	, 	,C		#r** , ,dD)) ,dhhv.>.> ,IId6l+++,	, Lr   c                     |                     dg           D ]L}t          D ]B}t          |                     |          t                    s+ fd||         D             ||<   CMdS )ud   Убрать устаревшую запись из старых патчей в манифесте.r   c                F    g | ]}|                     d           k    |S r(   r   )r2   fr$   s     r   
<listcomp>z4purge_path_from_previous_patches.<locals>.<listcomp>2  s-    III155==D3H3H!3H3H3Hr   N)r   r   r   list)r$   r   r   r`   s   `   r    purge_path_from_previous_patchesr   ,  s    i,, J J" 	J 	JCeiinnd33 IIIIU3ZIIIE#JJ	JJ Jr   	new_filesknown_filesexistingscan_full_client(tuple[list[dict], list[dict], list[str]]c                x   t          |          }g }g }g }| D ]}|d         }	|                    |	          }
|
|                    |           7|
                    d          |d         k    r |
                    d          |d         k    rv|	|v r;|                    |	           |                    |           t          |	|           |                    |           d | D             }t	          |          D ]z}|                                D ]c\  }}
||d         k    s||v r||vr|
                    d          |d         k    r)||vr%|                    |           t          ||           d{|r"|D ]}	|	|vr|	|vr|                    |	            ||t          t          |                    fS )u   
    Изменённые файлы патчей: files_removed (старый) + files_added (новый).
    Файлы базового client — files_updated.
    r$   NrT   rU   c                    h | ]
}|d          S r(   r   )r2   r   s     r   	<setcomp>z)classify_patch_changes.<locals>.<setcomp>U  s    ...q6...r   )r   r   rk   r   r   itemsrc   r   )r   r   r   r   patch_pathsr   r   files_removednew_filer$   old	new_pathsold_paths                r   classify_patch_changesr   5  s     #8,,K K "M!M + +ood##;x(((776??hv...3776??hvFV3V3V;  &&&x(((,T8<<<<  ****..I...I%% E E(..00 	E 	EMHc8F+++x9/D/D{**wwv(6"222x}7T7T$$X...08DDD	E  + 	+ 	+D9$$])B)B$$T***vc-.@.@'A'AAAr   r   	list[str]c                   |sd S t          |          |rO|                     di                               dg           }fd|D             |                     di           d<   |                     dg           D ]K}t          D ]A}t	          |                    |          t
                    rfd||         D             ||<   BLd S )Nr   ru   c                (    g | ]}|d          v|S r(   r   )r2   r]   removed_sets     r   r   z.apply_removals_to_manifest.<locals>.<listcomp>n  s.     6
 6
 6
T&\-L-LD-L-L-Lr   r   c                B    g | ]}|                     d           v|S r(   r   )r2   r   r   s     r   r   z.apply_removals_to_manifest.<locals>.<listcomp>t  s.    "b"b"bvVaAaAa1AaAaAar   )r   r   
setdefaultr   r   r   )r   r   r   client_files
prev_patchr`   r   s         @r   apply_removals_to_manifestr  h  s
    m$$K 
||Hb1155grBB6
 6
 6
 6
)6
 6
 6
Hb))'2 ll9b11 c c
" 	c 	cC*..--t44 c"b"b"b"bjo"b"b"b
3	cc cr   FTpatch_base_urlc                T   |rdnd}t          d|            t          d|             t          d|            t          d| d           t          |          }	|	s3t          d| d	t          j        
           t          j        d           t          j        t          j                  }
t          | t                                }t                       |s/t          dt          j        
           t          j        d           |s[t          ||| ||          }t          ||          \  }}|r|rt          d| d| d| d           n|r|st          d| d           t          |	          }t          |||	|          \  }}}|r3|s1t          dt          |           dt          |           d           n|st          d           t!          ||z   d           }t#          d |D                       }|s1|s/t          dt          j        
           t          j        d           t%          |	||           ||t'                      |                    d          dz   |t          |          t!          |d           t!          |d           ||d
}|	                    d i           }||d!<   t-          |          |d"<   t'                      |d#<   t/          |	d$                    |	                    d%g                               |           t3          ||	           t          j        t          j                  |
z
                                  }t          d&| d'           t          d(t          |                      t          d)t          |                      t          d*t          |                      t          d+|d,z  d-d.           t          d/|d0d1           |r&t          d2           |D ]}t          d3|            d S d S )4Nu3   полный клиент (с удалениями)u"   только файлы патчаu$   
🔧 Генерация патча r   u   📋 Режим: r   r   u(   ❌ Манифест не найден: u@   . Сначала создайте полный манифест.rX   r   r   uK   ❌ В каталоге не найдено файлов для патча.u$   ℹ️  Убран префикс «u	   /» из uD    путей (в манифесте: Data/..., на CDN: .../patches/z
/Data/...)u   ℹ️  Префикс «uJ   /» не найден в путях — оставлены как естьuE   ℹ️  Изменённые файлы патчей: удалить u   , добавить u*    (старые пути → files_removed)u   ℹ️  Удаления базового клиента не вычисляются (каталог = только патч). Для diff всего клиента — --scan-full-client.c                    | d         S r[   r   r\   s    r   r^   z generate_patch.<locals>.<lambda>  s
    tF| r   r_   c              3  &   K   | ]}|d          V  dS r   r   r   s     r   r4   z!generate_patch.<locals>.<genexpr>  s&      ::dT&\::::::r   ud   ❌ Нет изменений относительно манифеста — патч пустой.r0   c                    | d         S r[   r   r\   s    r   r^   z generate_patch.<locals>.<lambda>  s
    DL r   c                    | d         S r[   r   r\   s    r   r^   z generate_patch.<locals>.<lambda>  s
    V r   )
idr   r   r   r   r   r   r   r   ru   r   r   r   rV   r   r   u   
✅ Патч u%    сохранён в манифестu&   📥 Добавлено файлов: u&   🔄 Обновлено файлов: u"   🗑 Удалено файлов: u   📦 Размер патча: i   z.1fu    МБr   r   r   u$   
🗑 Удалённые файлы:z   - )rp   r   rq   rr   exitr   r   r   r   r|   r   r   r   r   r   r   rc   r   r  r   r   r  r#   r   rk   r   r   )rG   r   r   r  r   r   r   r   
mode_labelr   r   r   r   stripped_countr   r   r   r   patch_files
patch_sizer   r   r   r*   s                           r   generate_patchr  w  sh    K[  EFF  aEJ	
<(
<
<===	
.*
.
.///	
*j
*
*+++	
.N
.
.
.///V$$H v	
 	
 	
 	
 	l8<((G:?3D3DEEEI	GGG [beblmmmm B)x\:
 
 %6i$H$H!	> 	Bn 	B`v ` ` ` `KS` ` `     	BN 	B  Af  A  A  A  B  B  B%h//K0F;*:1 1-K  	
- 	
]TWXeTfTf ] ] #K 0 0] ] ]	
 	
 	
 	
  
L	
 	
 	

 }4:S:STTTK::k:::::J } t{~  |F  	G  	G  	G  	Gx8HIII ii"))#..4 ;''k/H/HIII3L3LMMM& E "":r22H!HY#G,,HW$YYH\Xh'(((	2&&--e444&(###|HL))G3BBDDG	
KH
K
K
KLLL	
E3{3C3C
E
EFFF	
G3}3E3E
G
GHHH	
Cs=/A/A
C
CDDD	
N:+C
N
N
N
NOOO	
,W
,
,
,
,--- &5666% 	& 	&H$($$%%%%& &	& 	&r   argparse.ArgumentParserc                 ~   t          j        d          } |                     dt          d           |                     dt          dt           d	           |                     d
t
          dt
           d	           |                     dt          t          dt           d           |                     ddd d           |                     ddd           |                     ddd           |                     dd d	           |                     dd dd            |                     d!dd"           | S )#NuS   Генерация nested-манифеста клиента WoW (client + patches).)descriptionrG   u~   Каталог клиента (полный манифест) или каталог с файлами патча (--add-patch))typehelpz	--versionu6   Версия клиента (по умолчанию: ))defaultr  z
--base-urluN   CDN base URL для базового клиента (по умолчанию: z--outputu>   Путь к JSON-манифесту (по умолчанию: )r  r  r  z	--mirrorsrJ   uQ   Дополнительные CDN-зеркала (origin или URL с путём))nargsr  r  z--add-patchPATCH_IDuh   Добавить патч к существующему манифесту (например: patch-002))metavarr  z--scan-full-client
store_trueu   Сканировать полную установку и вычислять удалённые файлы (иначе — только каталог патча))actionr  z--patch-urluU   Base URL патча (по умолчанию: https://dl.neix.ru/patches/<PATCH_ID>/)z--strip-prefixPREFIXu   Убрать префикс из path (например patch-001). По умолчанию определяется автоматически)r  r  r  z--no-strip-prefixuH   Не убирать префикс каталога патча из path)argparseArgumentParseradd_argumentr   DEFAULT_VERSIONDEFAULT_BASE_URLDEFAULT_OUTPUT)parsers    r   build_parserr'    s   $i  F  N    
 XoXXX    
  q^nqqq    
 _n___	     `	     w    
  r    
 d    
  _	     W    
 Mr   c            
        t                      } |                                 }|j                                                                        }|j                                                                        }	 |j        rA|j        }|j        pd| d}t          |||j	        |||j
        |j        |j                    n#t          ||j	        |j        ||j                   n]# t           $ r)}t#          d| t$          j                   Y d }~dS d }~wt(          $ r t#          dt$          j                   Y d	S w xY wd
S )Nzhttps://dl.neix.ru/patches/r0   )rG   r   r   r  r   r   r   r   )rG   r   r   r   r   u   ❌ rX   r   u0   
❌ Прервано пользователем   r   )r'  
parse_argsrG   
expanduserresolver   	add_patch	patch_urlr  r   r   r   no_strip_prefixr   r   r   rb   rp   rq   rr   KeyboardInterrupt)r&  argsrG   r   r   r.  r{   s          r   mainr2    sx   ^^FD++--5577J[##%%--//F> 	~HS*S*S*S*SI%!(!%!6!.#33	 	 	 	 	 #%       lSll,,,,qqqqq   A
SSSSss 1s   :A+C& &
E 0D(E ?E __main__)r   r   )r   r   r   r   )r$   r   r   r   )r*   r   r+   r   r   r,   )r9   r   r   r   r&   )rG   r   r   rH   )r$   r   r   r}   )r$   r   r   r   r   r   )r   r   r   r   )r   r   )rG   r   r   r   r   r   r   r   r   r   r   r   )r   r   r   r   )ru   rH   r   r   r   r   r   r   r   r,   r   r   )ru   rH   r   r   r   r   )r   r   r   r   )r   r   r   r   )r$   r   r   r   r   r   )
r   rH   r   r   r   r   r   r,   r   r   )r   r   r   r   r   r,   r   r   )FNT)rG   r   r   r   r   r   r  r   r   r   r   r,   r   r   r   r,   r   r   )r   r  )r   r   ).__doc__
__future__r   r   r<   r   rq   r   r   pathlibr   r#  r$  __file__r,  r   r%  ri   r5   r7   r@   r   r#   r)   r8   rF   r|   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r'  r2  __name__
SystemExitr   r   r   <module>r:     s   , # " " " " "    



 ' ' ' ' ' ' ' '      / h''))07:PP
BBB	D 
I I I I            3 3 3 3 3l! ! ! !   1 1 1 1   *. *. *. *.Z   " " " "J   ( <      J J J J0B 0B 0B 0Bfc c c c* ##i& i& i& i& i&X9 9 9 9x$ $ $ $N z
*TTVV

 r   