+
    �¤ëiµ)  ã                  óâ   € R t ^ RIHt ^ RIt^ RIt^ RIHt ^ RIHtH	t	H
t
 ^ RIt^ RIHt 0 RmtR tR R ltR	 R
 ltR R ltR R ltR R ltR R ltR R ltR t]R8X  d
   ]! 4        R# R# )aX  
Move PartKeepr attachment files into PartsInventory upload layout.

Behavior:
1) Reads image attachment metadata from PartKeepr.PartAttachment.
2) Recursively searches a source root (default: /var/www/html/Server/files)
   for matching filenames.
3) Moves files to destination upload tree:
     <dest-root>/parts/<part_id>/<filename>[.<extension>]
4) Inserts/updates parts_inventory.part_images rows for moved files.

This script assumes parts_inventory.parts.id == PartKeepr.Part.id
(which matches the rebuild SQL workflow that preserves IDs).

Usage example:
  python scripts/move_partkeepr_attachments_from_files.py     --host 127.0.0.1 --port 3306 --user root --password secret     --source-db PartKeepr --target-db parts_inventory     --source-root /var/www/html/Server/files     --dest-root /var/www/html/Server/partsinventory_uploads     --dry-run
)ÚannotationsN)ÚPath)ÚDictÚIterableÚList)Ú
DictCursorc                 ó°  € \         P                  ! R R7      p V P                  RRR7       V P                  R\        RR7       V P                  RRR7       V P                  R	RR7       V P                  R
RR7       V P                  RRR7       V P                  RRR7       V P                  RRR7       V P                  RRR7       V P	                  4       # )z@Move PartKeepr attachment files to PartsInventory upload layout.)Údescriptionz--hostT)Úrequiredz--portiê  )ÚtypeÚdefaultz--userz
--passwordz--source-dbÚ	PartKeepr)r   z--target-dbÚparts_inventoryz--source-rootz/var/www/html/Server/filesz--dest-rootz+/var/www/html/Server/partsinventory_uploadsz	--dry-runÚ
store_true)Úaction)ÚargparseÚArgumentParserÚadd_argumentÚintÚ
parse_args)Úparsers    ÚNc:\Users\Bill\Software\Server\scripts\move_partkeepr_attachments_from_files.pyr   r   $   sÒ   € Ü×$Ò$Ð1sÔt€FØ
×Ñ˜¨4ÐÔ0Ø
×Ñ˜¤s°DÐÔ9Ø
×Ñ˜¨4ÐÔ0Ø
×Ñ˜¨tÐÔ4Ø
×Ñ˜¨{ÐÔ;Ø
×Ñ˜Ð/@ÐÔAØ
×Ñ˜Ð1MÐÔNØ
×Ñ˜Ð/\ÐÔ]Ø
×Ñ˜¨LÐÔ9Ø×ÑÓÐó    c          
     ó,   € V ^8„  d   QhRRRRRRRRRR/# )é   ÚhostÚstrÚportr   ÚuserÚpasswordÚdatabase© )Úformats   "r   Ú__annotate__r#   2   s/   € ÷ 	ñ 	�#ð 	˜Sð 	¨ð 	°sð 	Àcñ 	r   c           
     óD   € \         P                  ! V VVVV\        R R7      # )F)r   r   r   r   r    ÚcursorclassÚ
autocommit)ÚpymysqlÚconnectr   )r   r   r   r   r    s   &&&&&r   r(   r(   2   s(   € Ü�?Š?ØØØØØÜØôð r   c               ó    € V ^8„  d   QhRRRR/# )r   ÚrowÚdictÚreturnÚboolr!   )r"   s   "r   r#   r#   >   s   € ÷ 
=ñ 
=˜Tð 
= dñ 
=r   c                óJ  a€ V P                  R 4      ^8X  d   R# V P                  R4      ;'       g    RP                  4       pVP                  R4      '       d   R# V P                  R4      ;'       g    RP                  4       P                  4       pV'       d    RVP	                  R4       2\
        9   d   R# V P                  R4      ;'       g    RP                  4       o\        ;QJ d#    V3R l\
         4       F  '       g   K   R# 	  R	# ! V3R l\
         4       4      # )
ÚisImageTÚmimetypeÚ zimage/Ú	extensionÚ.Úoriginalnamec              3  óF   <"  € T F  pSP                  V4      x € K  	  R # 5i©N)Úendswith)Ú.0ÚextÚoriginals   & €r   Ú	<genexpr>Ú&is_image_attachment.<locals>.<genexpr>H   s   øé € Ð<±¨#ˆx× Ñ  ×%Ð%³ùs   ƒ!F)ÚgetÚlowerÚ
startswithÚstripÚlstripÚ
IMAGE_EXTSÚany)r*   r0   r2   r:   s   &  @r   Úis_image_attachmentrD   >   sÚ   ø€ Ø
‡w�wˆyÓ˜QÔÙØ—‘˜
Ó#×)Ð) r×0Ñ0Ó2€HØ×Ñ˜8×$Ò$ÙØ—‘˜Ó%×+Ð+¨×2Ñ2Ó4×:Ñ:Ó<€Iß�q˜×)Ñ)¨#Ó.Ð/Ð0´JÔ>ÙØ—‘˜Ó'×-Ð-¨2×4Ñ4Ó6€Hß‹3Ô<µÓ<�3Œ3Ð<Š3Ð<ˆ3Ô<µÓ<Ó<Ð<r   c               ó    € V ^8„  d   QhRRRR/# )r   Úsource_rootr   r,   z3tuple[Dict[str, List[Path]], Dict[str, List[Path]]]r!   )r"   s   "r   r#   r#   K   s   € ÷ ñ  $ð Ð+^ñ r   c                óT  € / p/ pV P                  R 4       F�  pVP                  4       '       g   K  VP                  VP                  P	                  4       . 4      P                  V4       VP                  VP                  P	                  4       . 4      P                  V4       K�  	  W3# )Ú*)ÚrglobÚis_fileÚ
setdefaultÚnamer>   ÚappendÚstem)rF   Úby_nameÚby_stemÚps   &   r   Úbuild_file_indexrR   K   s„   € Ø%'€GØ%'€GØ×Ñ˜sÖ#ˆØ�y‰y�{Š{ÙØ×Ñ˜1Ÿ6™6Ÿ<™<›>¨2Ó.×5Ñ5°aÔ8Ø×Ñ˜1Ÿ6™6Ÿ<™<›>¨2Ó.×5Ñ5°aÖ8ñ	 $ð
 ÐÐr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úfilenamer   r2   ú
str | Noner,   zIterable[str]r!   )r"   s   "r   r#   r#   V   s!   € ÷ 	ñ 	˜cð 	¨jð 	¸]ñ 	r   c                óN  € T ;'       g    R P                  4       pV'       g   . # V0pT;'       g    R P                  4       P                  R4      pV'       dM   VP                  4       P                  RVP                  4        24      '       g   VP	                  V RV 24       V# )r1   r3   )r@   rA   r>   r7   Úadd)rT   r2   ÚcleanedÚnamesr9   s   &&   r   Úcandidate_namesrZ   V   s‡   € Ø�~ˆ~˜2×$Ñ$Ó&€GßØˆ	ØˆI€EØ�?ˆ?˜×
!Ñ
!Ó
#×
*Ñ
*¨3Ó
/€Cß
Ø�}‰}‹×'Ñ'¨!¨C¯I©I«K¨=Ð(9×:Ò:Ø�I‰I˜˜	  3 %Ð(Ô)Ø€Lr   c               ó0   € V ^8„  d   QhRRRRRRRRRR	R
R/# )r   rO   zDict[str, List[Path]]rP   rT   r   r2   rU   Úused_sourcesz	set[Path]r,   zPath | Noner!   )r"   s   "r   r#   r#   b   sD   € ÷  ñ  Ø"ð à"ð ð ð ð ð	 ð
 ð ð ñ r   c                óˆ  € . p\        W#4       F2  pVP                  V P                  VP                  4       . 4      4       K4  	  V'       ge   \	        T;'       g    R P                  4       4      P                  P                  4       pV'       d"   VP                  VP                  V. 4      4       V'       g   R# . p\        4       p	V F8  p
\        V
4      pW¹9   d   K  V	P                  V4       VP                  V
4       K:  	  VP                  R R7       V F%  p
W¤9   d   K  V
P                  4       '       g   K#  V
u # 	  R# )r1   Nc                óB   € \        V P                  4      \        V 4      3# r6   )ÚlenÚpartsr   )rQ   s   &r   Ú<lambda>Ú"pick_source_file.<locals>.<lambda>|   s   € œs 1§7¡7›|¬S°«VÑ4r   )Úkey)rZ   Úextendr=   r>   r   r@   rN   Úsetr   rW   rM   ÚsortÚexists)rO   rP   rT   r2   r\   ÚmatchesrL   rN   ÚuniqueÚseenrQ   rc   s   &&&&&       r   Úpick_source_filerk   b   s÷   € ð €GÜ Ö4ˆØ�‰�w—{‘{ 4§:¡:£<°Ó4Ö5ñ 5çä�X—^�^ ×*Ñ*Ó,Ó-×2Ñ2×8Ñ8Ó:ˆßØ�N‰N˜7Ÿ;™; t¨RÓ0Ô1ßÙà€FÜ‹5€DÛˆÜ�!‹fˆØŒ;ÙØ�‰�ŒØ�‰�aÖñ ð ‡K�KÑ4€KÔ5ÛˆØÔÙØ�8‰8�:Œ:ØŠHñ	 ñ
 r   c               ó    € V ^8„  d   QhRRRR/# )r   Úpart_idr   r,   r-   r!   )r"   s   "r   r#   r#   …   s   € ÷ *ñ *¨ð *°ñ *r   c                óL   € V P                  R V34       V P                  4       RJ# )z*SELECT id FROM parts WHERE id = %s LIMIT 1N©ÚexecuteÚfetchone)Údst_currm   s   &&r   Úensure_part_existsrs   …   s'   € Ø‡O�OÐ@À7À*ÔMØ×ÑÓ TÐ)Ð)r   c               ó0   € V ^8„  d   QhRRRRRRRRRRR	R/# )
r   rm   r   Úrel_pathr   Úoriginal_nameÚ
is_primaryÚdry_runr-   r,   r!   )r"   s   "r   r#   r#   Š   s9   € ÷ ñ ¨Cð ¸3ð Èsð Ð`cð Ðnrð Ðw{ñ r   c                óœ   € V P                  R W34       V P                  4       pV'       d   R# V'       d   R# V P                  RWW434       R# )zj
        SELECT id FROM part_images
        WHERE part_id = %s AND file_path = %s
        LIMIT 1
        FTz‘
        INSERT INTO part_images (part_id, file_path, original_filename, source, is_primary)
        VALUES (%s, %s, %s, 'uploaded', %s)
        ro   )rr   rm   ru   rv   rw   rx   r*   s   &&&&&& r   Úensure_part_image_rowrz   Š   sW   € Ø‡O�Oð	ð
 
Ðôð ×
Ñ
Ó
€Cß
ÙßÙØ‡O�Oð	ð 
˜MÐ6ôñ r   c                 ó:  € \        4       p \        V P                  4      p\        V P                  4      pVP	                  4       '       g   \        R V 24      h\        V P                  V P                  V P                  V P                  V P                  4      p\        V P                  V P                  V P                  V P                  V P                  4      p^ p^ p^ p^ p^ p	^ p
^ p \        V4      w  rÍVP	                  4       '       d   \        V4      M/ / 3w  rï\        4       pVP                  4       ;_uu_ 4       pVP                  4       ;_uu_ 4       pVP!                  R4       VP#                  4        Uu. uF  p\%        V4      '       g   K  VNK  	  ppV EFn  p\'        VR,          4      p\)        VV4      '       g   V^,          pK3  VP+                  R4      ;'       g    RpVP+                  R4      pVP+                  R4      ;'       g    TP-                  4       ;'       g    Tp\/        WÍVVV4      pRpVf   \/        WïVVV4      pVe   R	pV^,          pVf   V^,          pKÒ  VP0                  pV'       g&   V'       d   R
\3        V4      P5                  R
4       2pV'       g   RpV'       d   \        V4      P6                  MVP6                  ;'       g    RVR,           2p\        R4      \3        V4      ,          V V 2,          pVV,          pV'       g$   VP9                  4       VP9                  4       8X  d   V	^,          p	EM3VV9   d   V
^,          p
V^,          pEKÓ  VP	                  4       '       g   V^,          pEKõ  V P:                  '       gÊ   VP<                  P?                  R	R	R7       VP	                  4       '       dn   ^p VP<                  V RV V 2,          p V P	                  4       '       g0   T p\        R4      \3        V4      ,          V P@                  ,          pMV^,          pKk  \B        PD                  ! \3        V4      \3        V4      4       V^,          pVPG                  V4       VP!                  RV34       \'        VPI                  4       R,          4      ^ 8X  d   ^M^ p!\K        VV\3        V4      PM                  RR4      VV!V P:                  R7      p"V"'       g   EKe  V^,          pEKq  	  RRR4       RRR4       V P:                  '       d   VPO                  4        MVPQ                  4        \S        RRV 2RV	 2RV 2RV 2RV 2RV
 2RV 2RV P:                   24	        TPW                  4        TPW                  4        R# u upi   + '       g   i     L¨; i  + '       g   i     L³; i  \T         d    TPO                  4        h i ; i  TPW                  4        TPW                  4        i ; i)zSource root does not exist: zª
                SELECT id, part_id, filename, originalname, extension, isImage, mimetype
                FROM PartAttachment
                ORDER BY id
                rm   rT   r1   r2   r4   FNTr3   ú.jpgÚattachment_Úidr`   )ÚparentsÚexist_okÚ_z8SELECT COUNT(*) AS c FROM part_images WHERE part_id = %sÚcÚ\Ú/)rm   ru   rv   rw   rx   zAttachment move complete:zmoved=zalready_in_place=zpart_images_inserted=zmissing_source_file=zmissing_target_part=zreused_source_match=zfound_in_dest=zdry_run=),r   r   rF   Ú	dest_rootrg   ÚRuntimeErrorr(   r   r   r   r   Ú	source_dbÚ	target_dbrR   re   Úcursorrp   ÚfetchallrD   r   rs   r=   r@   rk   Úsuffixr   rA   rN   Úresolverx   ÚparentÚmkdirrL   ÚshutilÚmoverW   rq   rz   ÚreplaceÚrollbackÚcommitÚprintÚ	ExceptionÚclose)#ÚargsrF   r…   Úsrc_connÚdst_connÚmovedÚinserted_rowsÚmissing_source_fileÚmissing_target_partÚalready_in_placeÚreused_source_matchÚfound_in_destÚsrc_by_nameÚsrc_by_stemÚdst_by_nameÚdst_by_stemr\   ÚsrcÚdstr*   Úattachmentsrm   rT   r2   rv   Úsrc_fileÚsource_is_destr‹   Ú	safe_stemru   Ú	dest_fileÚidxÚaltrw   Úinserteds#                                      r   Úmainr¯   ¢   s:  € Ü‹<€DÜ�t×'Ñ'Ó(€KÜ�T—^‘^Ó$€Ià×Ñ×ÒÜÐ9¸+¸ÐGÓHÐHä�t—y‘y $§)¡)¨T¯Y©Y¸¿¹ÀtÇ~Á~ÓV€HÜ�t—y‘y $§)¡)¨T¯Y©Y¸¿¹ÀtÇ~Á~ÓV€Hà€EØ€MØÐØÐØÐØÐØ€MðkÜ#3°KÓ#@Ñ ˆØBK×BRÑBR×BTÒBTÔ#3°IÔ#>Ð[]Ð_aÐZbÑ ˆÜ"%£%ˆà�_‰_×Ô # x§¡×'8Ô'8¸CØ�K‰Kðôð +.¯,©,¬.ÓU©. 3Ô<OÐPS×<TŸ3˜3©.ˆKÐUä"�Ü˜c )�nÓ-�Ü)¨#¨w×7Ò7Ø'¨1Õ,Ð'ÙàŸ7™7 :Ó.×4Ð4°"�ØŸG™G KÓ0�	Ø!$§¡¨Ó!8×!DÐ!D¸H× KÑ KÓ M× YÐ YÐQY�ä+¨KÀhÐPYÐ[gÓh�Ø!&�ØÒ#ä/°È(ÐT]Ð_kÓl�HØÒ+Ø)-˜Ø%¨Õ*˜ØÒ#Ø'¨1Õ,Ð'Ùà!Ÿ™�ß§)Ø ¤ Y£×!6Ñ!6°sÓ!;Ð <Ð=�FßØ#�Fç4<œT (›^×0Ò0À(Ç-Á-×mÐmÐVaÐbeÐfjÕbkÐalÐTm�	Ü ›=¬3¨w«<Õ7¸Y¸KÈÀxÐ:PÕP�Ø%¨Õ0�	ç! X×%5Ñ%5Ó%7¸9×;LÑ;LÓ;NÔ%NØ$¨Õ)Ò$à <Ô/Ø+¨qÕ0Ð+Ø+¨qÕ0Ð+Ú Ø#Ÿ?™?×,Ò,Ø+¨qÕ0Ð+Ú ØŸ<Ÿ<˜<Ø!×(Ñ(×.Ñ.°tÀdÐ.ÔKØ$×+Ñ+×-Ò-Ø"#˜CØ"&Ø&/×&6Ñ&6¸I¸;ÀaÈÀuÈVÈHÐ9UÕ&U Ø'*§z¡z§|¢|Ø03 IÜ/3°G«}¼sÀ7»|Õ/KÈcÏhÉhÕ/V HØ$)Ø # q¥¢ÜŸš¤C¨£M´3°y³>ÔBØ˜Q•J�EØ ×$Ñ$ XÔ.à—‘ÐVÐY`ÐXbÔcÜ"% c§l¡l£n°SÕ&9Ó":¸aÔ"?™QÀQ�
ä0ØØ#Ü  ›]×2Ñ2°4¸Ó=Ø"/Ø)Ø ŸL™Lô�÷ ’8Ø! QÕ&“MñK #÷ (9×ðb �<�<ˆ<Ø×ÑÕà�O‰OÔäØ'Ø�U�GÐØÐ 0Ð1Ð2Ø# M ?Ð3Ø"Ð#6Ð"7Ð8Ø"Ð#6Ð"7Ð8Ø"Ð#6Ð"7Ð8Ø˜]˜OÐ,Ø�t—|‘|�nÐ%õ
	
ð 	�‰ÔØ�‰Öùò} V÷ (9×'8ú××ûôB ô Ø×ÑÔØðûð 	�‰ÔØ�‰Õúsº   Ã*AW Å WÅ$V2	Å=V-ÆV-ÆAV2	Ç*+V2	ÈV2	È.AV2	ÊV2	Ê$V2	Ê2	V2	Ê<'V2	Ë$B#V2	ÎV2	Î&2V2	Ï2V2	ÐC2V2	ÔV2	ÔWÔW Ô4AW Ö-V2	Ö2WÖ=W×W	×W ×W5×5W8 ×8"XÚ__main__>	   ú.bmpú.gifr|   ú.pngú.svgú.tifú.jpegú.tiffú.webp)Ú__doc__Ú
__future__r   r   r�   Úpathlibr   Útypingr   r   r   r'   Úpymysql.cursorsr   rB   r   r(   rD   rR   rZ   rk   rs   rz   r¯   Ú__name__r!   r   r   Ú<module>r¿      sm   ðñõ, #ã Û Ý ß 'Ñ 'ã Ý &âX€
òõ	õ
=õõ	õ õF*õ
ò0~ðB ˆzÔÙ†Fñ r   