
    9iPf                     B   S r SSKrSSKrSSKrSSKrSSKrSSKrSSKJr  SSK	J
r
  SSKJrJr  SSKJr  SSKJr  SSKJrJr  SS	KJr  SS
KJr  SSKJr  \R0                  r\R2                  r\R4                  r\R6                  rS rS rS rS rS r S r!\!S 5       r"\!S 5       r#S r$S r%S r&S r'S\(S\(4S jr)S r*\!S 5       r+S r,S r-S r.S  r/S! r0S" r1S0S#\2S$\34S% jjr4S1S& jr5S' r6S( r7\" S)\Rp                  " 5       S*9S+ 5       r9S,r:\:S--  S--  r;\;4S. jr<S2S/ jr=g)3u^   
本文件实现了请求hub

Authors: xiangyiqing(xiangyiqing@baidu.com)
Date:    2023/07/24
    N)quote)	ErrorEnum)configlog)err_resp)gen_ISO_format_datestr)file_to_base64thread_executor)$create_sha256_file_and_encode_base64)VERSION)RequestErrorc           	         [        [        5       H/  n [        R                  " XUU[        S9nUR                  5       s  $    [        [        R                  R                   WSS W5      $ ! [        R                  R                   aA    SR                  WR                  5      nUR                  n[        R                  " U5         M  f = f)z
request aistudio hub
headersdatatimeout-Response body does not contain valid json: {}N  )rangeCONNECTION_RETRY_TIMESrequestsrequestCONNECTION_TIMEOUTjson
exceptionsJSONDecodeErrorformattextstatus_coder   debugr   r   INTERNAL_ERRORcode)methodurlr   r   _responseerr_msgbiz_codes           Y/var/www/html/land-doc-ocr/venv/lib/python3.13/site-packages/aistudio_sdk/requests/hub.py_request_aistudio_hubr*   %   s     )*	''W-1;MOH==?"	 + I,,11DSM 	 ""22 	ELLX]][G++HIIg	s   *A++AC
	C
c                     [        U S   S9nU R                  S5        SR                  [        R                  " S[
        R                  S9[
        R                  5      nU R                  5        VVs0 s H  u  p4X4_M	     nnn[        R                  " SU 35        [        R                  " SU 35        [        R                  " U5      n[        SX!U5      nU$ s  snnf )	u   
请求AIStudio hub: 模型库
tokenr,   {}{}STUDIO_MODEL_API_URL_PREFIXdefaultzrequest_aistudio_hub url: zrequest_aistudio_hub body: POST)_header_fillpopr   osgetenvr   #STUDIO_MODEL_API_URL_PREFIX_DEFAULTHUB_URLitemsr   r    r   dumpsr*   kwargsr   r$   kvbodypayloadresps           r)   request_aistudio_hubrB   7   s     1G
JJw
--
		/9c9cdC
 $\\^,^TQAD^D,II*3%01II+D623jjG w?DK -   2Cc                     [        U S   S9nU R                  S5        SR                  [        R                  " S[
        R                  S9[
        R                  5      nU R                  5        VVs0 s H  u  p4X4_M	     nnn[        R                  " SU 35        [        R                  " SU 35        [        R                  " U5      n[        SX!U5      nU$ s  snnf )	u    
请求AIStudio hub：应用库
r,   r-   r.   r/   r0   z"request_aistudio_app_service url: z#request_aistudio_app_service body: r2   )r3   r4   r   r5   r6   r   r7   APP_SERVICE_URLr9   r   r    r   r:   r*   r;   s           r)   request_aistudio_app_servicerF   L   s    
 1G
JJw
--
		/9c9cdC
 $\\^,^TQAD^D,II23%89II3D6:;jjG w?DK -rC   c                     [        U S   S9nSR                  [        R                  " S[        R
                  S9[        R                  5      nUS[        U S   SS	9 S
3-   nSn Sn[        R                  " X2U[        S9nUR                  5       $ ! [        R                  R                   a     SR                  W5      nUR                  n Of = f[        [         R"                  R$                  USS U5      $ )u+   
请求AIStudio hub 查看repo可见权限
r,   r-   r.   r/   r0   z?repoId=repoId safez&authorization=1GET)r   r   r   Nr   )r3   r   r5   r6   r   r7   HUB_URL_VISIBLE_CHECKr   r   r   r   r   r   r   r   r   r   r!   r"   )r<   r   r$   r#   r'   r&   r(   s          r)   request_aistudio_repo_visiblerN   b   s     1G
--
		/9c9cd$$C (5!1;<<LM
MCF(##F,>@}}.. (AHHR''( I,,11DSM s   %+B ;CCc           	         [        [        5       GH*  n[        R                  " 5       nUR	                  XX#[
        S9nUR                  5         UR                  S;  a  [        R                  " SUR                   SUR                   35        [        R                  " SU SU 35        UR                  S:X  a  SOS	n[        U S
:X  a  [        R                  R                  O[        R                   R                  UR"                  R%                  5       SS U-   UR                  S9s  $ UR'                  5       s  $    g)z
request gitea
r         z
response:  z'potential network problem while request:  u   [仓库或分支不存在]rI   rL   Nr   r(   )r   r   r   Sessionr   r   closer   r   r    r   infor   r   GITEA_DOWNLOAD_FILE_FAILEDr"   GITEA_UPLOAD_FILE_FAILEDcontentdecoder   )r#   r$   r   r   r%   sessionr&   	extra_msgs           r)   _request_gitear`   }   s    )*""$??6Tf?gz1II
8==/83G3G2HIJHH>se1XJOP8@8L8LPS8S4Y[I"eO &@@EE1:1S1S1X1X$,,335ds;iG%-%9%9; ;
 ==?" +    c                    ^  U 4S jnU$ )z
time cost decorator
c                     > [         R                   " 5       nT" U 0 UD6n[         R                   " 5       nXB-
  n[        TR                   SUS S35        U$ )Nz done, time cost: .2fs)timeprint__name__)argsr<   
start_timeresultend_timeelapsed_timefuncs         r)   wrapper!timing_decorator.<locals>.wrapper   sR    YY[
t&v&99;,1,s1C1EFra    )rn   ro   s   ` r)   timing_decoratorrr      s     Nra   c           	      ,   [         R                  " 5       nUR                  XX#S[        S9nUR	                  5         UR
                  S;  a:  [        [        R                  R                  UR                  SS UR
                  S9$ UR                  5       $ )z
_upload proc
T)r   r   streamr   rP   Nr   rV   )r   rW   r   CONNECTION_TIMEOUT_UPLOADrX   r   r   r   r[   r"   r\   r   )r#   r$   r   r   r^   r&   s         r)   _uploadrv      s    
  GvG&*4M  OHMMO:-	::?? ((#.!)!5!57 	7 }}ra   c                    [         R                  " SU SU[        S9nUR                  S:X  a  0 nOUR                  S:X  aD   UR	                  5       S   n[        [        R                  R                  UUR                  5      nO<[        [        R                  R                  SUR                   3UR                  5      n[        UR                  R!                  SS	5      5      nS
n["        R"                  " USSSSS9n[%        US5       n	UR'                  U5       H.  n
UR)                  [+        U
5      5        U	R-                  U
5        M0     SSS5        UR/                  5         US	:w  a  UR0                  U:w  a  [3        S5        U$ ! [         R
                  R                   a    UR                  R                  5       n GNqf = f! , (       d  f       N= f)zd
Params
    :url: http url
    :download_path: download path
    :headers: headers
Returns
    file
rL   T)rt   r   r   rQ   rU   messagez Download failed, response code: zcontent-lengthr   i  2   iBzDownloading file)totalncolsunit
unit_scaledescwbNzERROR, something went wrong)r   r   CONNECTION_TIMEOUT_DOWNLOADr   r   r   r   r\   r]   r   r   FILE_NOT_FOUNDr"   rZ   intr   gettqdmopeniter_contentupdatelenwriterX   nrg   )r$   download_pathr   r&   retrx   
total_size
block_sizeprogress_barfiler   s              r)   	_downloadr      s    s4(CEH s"				$	0mmoi0G y//44 ,,. y;;@@:8;O;O:PQ ,,. X%%))*:A>?JJ99:Rdt"46L 
mT	"d))*5DD	*JJt 6 
#
 Q<>>Z7+,J3 ""22 	0&&--/G	0  
#	"s   F
 AG	
8GG	
Gc                 0    [        US9n[        XU5      nU$ )u"   
请求AIStudio gitea文件下载
r-   )r3   r   )r$   r   r,   r   ress        r)   request_aistudio_git_downloadr      s     'G
C
0CJra   c                     U  S[        USS9 S[        USS9 S[        USS9 3nUS:w  a  US[        USS9 3-  n[        US9n[        S	XgS5      n[        R                  " S
U SU 35        U$ )u   
请求AIStudio gitea 文件info
GET /api/v1/repos/{owner}/{repo}/contents/{filepath} 返回的文件的数据、大小、编码等metadata信息+文件内容，或者文件夹中的文件列表
/api/v1/repos/rI   rJ   /
/contents/master?ref=r-   rL   z...result of GET /contents/: )r   r3   r`   r   r    )		call_host	user_name	repo_name	file_pathrevisionr,   r$   r   r   s	            r)   request_aistudio_git_file_infor      s     K~eIB&?%@	yr	"#:eIB.G-HJC8uXB/011'G
b
1CII+I;b>?Jra   c                    [        US9nU  S[        USS9 S[        USS9 S[        USS9 3nSSU0/0n[        R                  " U5      n	[	        SXvU	S	9n
[
        R                  " S
U SU
 35        SU
;   a  U
nU$ SU
;  d  U
S   (       a  SU
S   S   ;  a2  [        [        R                  R                  [        U
5      SS 5      nU$ SU
S   S   S   0nU$ )u)   
请求AIStudio gitea 确认文件类型
r-   r   rI   rJ   /preupload/filespathr2   r   ...result of POST /preupload: r   
error_codelfsr   Nr   is_lfs)r3   r   r   r:   r`   r   r    r   r   GITEA_FAILEDr"   str)r   r   r   r   path_in_repor,   r   r$   paramsr@   rk   r   s               r)   request_aistudio_git_file_typer      s   
 'GKqyr231U925N4O{[`aipr[sZt
uC 	L
 F jj GFCw?FII.se2fX>?v J 
	fWofWoVWFX9Xy--22FDS)+ J fWoa(/
 Jra   c                    [        US9nU  S[        USS9 S[        USS9 S[        USS9 3nSU Vs/ s H  nSU0PM	     sn0n	[        R                  " U	5      n
[	        S	XvU
S
9n[
        R                  " SU SU 35        SU;   a  [        S5      eSU;  d  [        US   [        5      (       d  [        S5      e0 nUS    H3  nUR                  S5      nUR                  SS5      nU(       d  M/  XU'   M5     U$ s  snf )u   
批量请求 AIStudio gitea 确认多个文件是否为 LFS 类型

:param path_list: List[str]，每个元素是 repo 内的相对路径
:return: Dict[str, bool]，key 是路径，value 是是否为 LFS
r-   r   rI   rJ   r   r   r   r   r2   r   r   r   r   z-preupload fail, there is error_code in resultz#preupload fail, wrong result formatr   F)r3   r   r   r:   r`   r   r    
ValueError
isinstancelistr   )r   r   r   r   path_in_repo_listr,   r   r$   r   r   r@   rk   lfs_map	file_infor   s                  r)   request_aistudio_git_files_typer     s'    'G[IB'(%	*C)DKPUV^egPhOikC 	->?->T64.->?F jj GFCw?FII.se2fX>?vHJJ		j$&G&G>@@GG_	}}V$ue,4"DM	 % N' @s   C=upload_sectionreturnc           	      J   U R                  S0 5      nU(       a  UR                  S5      (       as  UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      S	.$ 0 $ )
u  
解析sts_token

"upload": {
    "href": "https://some-download.com",
    "header": {
        "Key": "value"
    },
    "sts_token": {
        "bosHost":""
        "bucketName": "",
        "key":"",
        "accessKeyId":"",
        "secretAccessKey":"",
        "sessionToken":"",
        "createTime":"",
        "expiration":""
        }
    "expires_at": "2016-11-10T15:29:07Z"
}
	sts_tokenaccessKeyIdbosHost
bucketNamekeysecretAccessKeysessionToken
expiration)bos_hostbucket_namer   access_key_idsecret_access_keysession_tokenr   )r   )r   r   s     r)   _parse_sts_tokenr   >  s    , "";3IY]]=11!i0$==6=='&]]=9!*/@!A&]]>:#--5
 	
 Ira   c           	      @   SSS.n[        XvS9nU  S[        USS9 S[        USS9 S3n	S	UUS
./SS/SSU 30SS.n[        R                  " U5      n
[	        SXU
5      n[
        R                  " SU 35        SU;   a  UnU$ SU;  d
  US   (       d2  [        [        R                  R                  [        U5      SS 5      nU$ US   S   nSU;   a  S	US   ;   a  SOSSU;   a  US   S	   S   OSSU;   a  [        US   S	   5      O0 SU;   a  US   S   S   OSS.nU$ )u@   
请求AIStudio gitea 申请上传LFS文件.
只支持单文件
z+application/vnd.git-lfs+json; charset=utf-8application/vnd.git-lfs+jsonContent-TypeAcceptr   r,   r   rI   rJ   z.git/info/lfs/objects/batchuploadoidsizezlfs-standalone-filebasicnamezrefs/heads/sha256)	operationobjects	transfersref	hash_algor2   z...result of POST /batch: r   r   Nr   r   actionsTFhrefverify)r   upload_hrefr   verify_href)r3   r   r   r:   r`   r   r    r   r   r   r"   r   r   )r   r   r   r   	file_sizer   r,   r   r   r$   r@   rk   r   tmps                 r)   "request_aistudio_git_upload_accessr   c  s    F0F &6GKqyr231U925N4OOj
kC  !
 "7
 k(,
 F" jj GFC':FII*6(34v J 
&	 y(9y--22FDS)+ J Y" (3.8s9~3MdSX?HC?O3y>(3F;UWGPTWGW)#i.*BC]_?HC?O3y>(3F;UW	
 Jra   c           
          [        US5       n[        R                  " SXU[        SS9nSSS5        SWR                  S   0$ ! , (       d  f       N= f)   
上传LFS文件到bos
rbPUTT)r   r   r   rt   NzContent-Md5)r   r   r   ru   r   )r$   path_or_fileobjr   r   r&   s        r)   _lfs_uploadr     sR    
 
ot	$##E3d,EdT 
% 8++M:;; 
%	$s   A
Ac                 6    SS0n[        USS9n[        XU5      $ )r   r   zapplication/octet-streamrI   r   )r3   r   )r$   r   r   r   s       r)   request_bos_uploadr     s(     89F&3GsW55ra   c                 |    SU ;   a"  U S   [         R                  R                  :w  a  gU (       a  SU ;  a  gU S   nU$ )u   
解析info_res
r   rI   sha)r   SUCCESSr"   )info_resold_shas     r)   get_exist_file_old_shar     sB     xH\$:i>O>O>T>T$Th.5/ra   c	                 J   [        XX'X85      n	[        U	5      n
U
S:X  a  SnOSn[        US9nU  S[        USS9 S[        USS9 S[        USS9 3nUU[	        XV5      S	[        5       [        5       S
.US.nUS:X  a  XS'   [        R                  " U5      n[        XX5      nU$ );   
请求AIStudio gitea 上传LFS指针文件（到仓库）
rI   r2   r   r-   r   rJ   r   r   Tauthor	committer)branch
new_branchr\   
lfsPointerdatesrx   r   )	r   r   r3   r   r   r   r   r:   r`   )r   r   r   r   commit_messager   r   r   r,   r   r   r#   r   r$   r   r@   r   s                    r)   #request_aistudio_git_upload_pointerr     s     .iI.6?H$X.G"} 'GK~eIB&?%@	yr	"#:eLr.J-KMC 7J,./1
 "
F ujj G
g
7CJra   c                     [        XX&X75      n[        U5      n	U	S:X  a  Sn
OSn
[        US9nU  S[        USS9 S[        USS9 S[        USS9 3nUU[	        XE5      S	S
.nU
S:X  a  XS'   [
        R                  " U5      n[        XX5      nU$ )r   rI   r2   r   r-   r   rJ   r   r   T)r   r   r\   r   r   )r   r   r3   r   r   r   r:   r`   )r   r   r   r   r   r   r   r,   r   r   r#   r   r$   r   r@   r   s                   r)   !request_single_git_upload_pointerr     s     .iI.6?H$X.G"} 'GK~eIB&?%@	yr	"#:eLr.J-KMC 7J	
F ujj G
g
7CJra   c                 N   [        XX&X75      n[        U5      n	U	S:X  a  Sn
OSn
U  S[        USS9 S[        USS9 S[        USS9 3n[        US9n[	        U5      nUUUS	[        5       [        5       S
.US.nU
S:X  a  XS'   [        R                  " U5      n[        XX5      nU$ )8   
请求AIStudio gitea 上传普通文件（到仓库）
rI   r2   r   r   rJ   r   r   r-   Fr   )r   r   r\   r   r   rx   r   )	r   r   r   r3   r	   r   r   r:   rv   )r   r   r   r   r   r   r   r,   r   r   r#   r$   r   base64_datar   r@   r   s                    r)   "request_aistudio_git_upload_commonr    s     .iI.6?H$X.G"} K~eIB&?%@	yr	"#:eLr.J-KMC'G 1K ,./1
 "
F ujj G
&w
0CJra   c                     [        XX%X65      n[        U5      nUS:X  a  Sn	OSn	U  S[        USS9 S[        USS9 S[        USS9 3n
[        US9n[	        U5      nUS	S
.nU	S:X  a  XS'   [
        R                  " U5      n[        XX5      nU$ )r   rI   r2   r   r   rJ   r   r   r-   F)r\   r   r   )r   r   r   r3   r	   r   r:   rv   )r   r   r   r   r   r   r,   r   r   r#   r$   r   r  r   r@   r   s                   r)    request_single_git_upload_commonr  0  s     .iI.6?H$X.G"} K~eIB&?%@	yr	"#:eLr.J-KMC'G 1K
 	
F ujj G
&w
0CJra   r   r   c           	      r   SSS.nUUS.n[        XCS9n[        R                  " SXU[        R                  " U5      S9n[
        R                  " SUR                   35        UR                  S;  a7  [        [        R                  R                  UR                  UR                  S	9$ UR                  5       $ )
z\
param
    call_host: verify url
    oid: sha256, without sha256prefix
    size: file size

r   r   r   r-   r2   )r   r   r   z...result of POST /verify: rP   rV   )r3   r   r   r   r:   r   r    r   r   r   r   r[   r"   )r   r   r   r,   r   r   headerr   s           r)    request_aistudio_verify_lfs_filer  X  s     70G
 F '/F


696PTPZPZ[aPb
cCII+CHH:67
j(	::??!$2 	2 xxzra   c                     U(       a  SU 3nOS[         R                  " SSS9 3nSU[        [        5      S.nU (       a  UR	                  U 5        U$ )u   
填充header
ztoken AISTUDIO_ACCESS_TOKENrI   r0   zapplication/json)r   AuthorizationzSDK-Version)r5   r6   r   r   r   )r   r,   authr   s       r)   r3   r3   s  sV     w		"92FGH*7|G
 vNra   c                     [         R                  " S[        R                  S9nU SU  SU SU 3n[	        US9n[
        R                  " XVS9nUR                  S:X  a!  S	UR                  5       R                  S
5      4$ g)uH   检查目标文件是否存在，返回 (exists: bool, sha: str or None)STUDIO_GIT_HOSTr0   r   r   r   r-   )r   rQ   Tr   )FN)	r5   r6   r   STUDIO_GIT_HOST_DEFAULTr3   r   r   r   r   )repo_idr   r   r,   hostr$   r   rA   s           r)   file_exists_and_shar    sx    99&0N0NODF.	D6xj
IC'G<<-D3TYY[__U+++ra   c                     SU  SU S3$ )u.   构造 LFS pointer 文件内容（纯文本）z6version https://git-lfs.github.com/spec/v1
oid sha256:z
size 
rq   )r   r   s     r)   get_lfs_pointer_contentr    s    vR1 1ra   F)disable_tqdmmax_workersc                 4   U u  pEpg [        XXC5      u  pU(       a5  [        R
                  R                  U5      n[        U=(       d    UU5      nOM[        US5       nUR                  5       nSSS5         [        R                  " W5      nUR                  S5      nUUUU(       a  SOSS	.nU	(       a  U	US
'   U$ ! [         a#  n
[        R                  " U S35         Sn
A
gSn
A
ff = f! , (       d  f       N= f! [         a    [        SU S35      ef = f)z
prepare body data
z request git error,skipNr   zutf-8u   ❌ Non-UTF8 content in z-. Consider LFS or base64 with encoding field.r   create)r   r   r\   r   r   )r  	Exceptionr   errorr5   r   getsizer   r   readbase64	b64encoder]   UnicodeDecodeErrorr   )qr  r   r,   r   
local_pathr   sha256_inputexistsr   er   content_b64fcontent_bytescontent_strentrys                    r)   prepare_entryr*    s   
 ./*Df)'TI wwz*:<;W<Y]^*d#qFFHM $	s **=9K%,,W5K
 !'XX	E eL3  		TF123 $#
 " 	s7
|Cpqrr	ss/   B< C,7'C= <
C)C$$C),
C:=DrQ      c                    / n/ nSnSnU  H  nUR                  SS5      nXq:  a  [        SUS-  S-  S S35      eXG-   U:  d0  US-   [        [        R                  R                  SS	5      5      :  a  UR                  U5        U/nUnSnM  UR                  U5        XG-  nUS-  nM     U(       a  UR                  U5        U$ )
u   
将 files 拆分为多个分组，每组最大总大小不超过 max_bytes,每组个数不超过1k
要求每个 file 是一个 dict，包含 'size' 字段（单位字节）。
r   r   u!   单个文件超过最大限制：r+  rd   MB   MAX_COMMIT_FILE_COUNT500)r   r   r   r5   environappend)r   	max_byteschunkscurrent_chunkcurrent_sizecurrent_countr   r   s           r)   split_files_by_sizer8    s    
 FMLMHHVQ'	 @TAQTXAXY\@]]_`aa$y0MA4EBJJNN#:EBC5DMM-(!FM$LM  &%LQM   m$Mra   c                    Uc  SSS.nUc  Un[         R                  " S5        [         R                  " S[        R                  " 5        35        [        XAX%S9nU V	s/ s H	  oc  M  U	PM     nn	[        U5      n
[        U
5      S:  a  [         R                  " S	5        [        R                  " S
[        R                  S9n[        US9nU SU S3n[        U
SS9 H  u  p[        U
5      S:X  a  UOU SU S[        U
5       S3nUUUUUS.n[        R                  " XUS9nUR                  S-  S:X  a&  [!        UR                  5        [!        SU S35        M  U H/  nU H&  u  nnnnUS   U:X  d  M  U R#                  US45        M(     M1     [!        SU SUR                   35        [!        UR$                  5        M     gs  sn	f )z
commit
NzAuto Commitzauto@example.com)r   emailzcalculate fileszcpu:)r  r   r,   r.  z'files will be commited in multi batchesr  r0   r-   r   z	/contents)startz (part r   ))r   rx   r   r   r   )r   r   d      u   ✅ Commit part z successful!r   zcommit failu   ❌ Commit part z	 failed: )r   rY   r    r5   	cpu_countr*  r8  r   r6   r   r  r3   	enumerater   postr   rg   r2  r   )log_listr  r   r   
file_quadsr,   r   r   r   itemfile_chunksr  r   r$   ichunkcommit_message_currentchunk_payloadrA   r)  r   r!  r   r"  s                           r)   commit_filesrJ    s    ~'2DE	HHIIR\\^$%&*VE#8edTeE8%e,K
;!:;99&0N0NOD'GF.	
3Ck336{3Cq3H"#71#Qs;/?.@B 	 -"
 }}SFs"a'$""#$QC|45>H:D*flV}, ](CD ?I  $QCy1A1A0BCD$))- 4 9s   G(G)rI   )NrI   )NN)>__doc__r  r5   rf   r   r   r   urllib.parser   aistudio_sdk.constant.err_coder   aistudio_sdkr   r   aistudio_sdk.utils.utilr   r   r	   r
   r   aistudio_sdk.constant.versionr   aistudio_sdk.errorsr   r   r   r   ru   r*   rB   rF   rN   r`   rr   rv   r   r   r   r   r   dictr   r   r   r   r   r   r   r  r  r   r   r  r3   r  r  r?  r*  MAX_PAYLOAD_MBMAX_PAYLOAD_BYTESr8  rJ  rq   ra   r)   <module>rU     s    	      4 $ , : C H 1 ,66 .. $@@ "<< $*,6#*  " + +\$>!F!T !d !J1h < <6
$N$N'T&PS  6$1 e@  A D "T)D0  *; @)ra   