ó
     X™jY#  ã                   ó  • 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rSSK	J
r
  SrSrSrSrSrS	rS
r " S S\5      rS rSS\S\4S jjrSS\SS4S jjrS\4S jrS rSS\4S jjrSS\4S\S\4S jjrSS\S\4S jjrg)aK  Lasting links for the documents Dealer OS's Purchases page shows
(2026-09-02).

The Mac Studio keeps every purchase's service history and V5 scans as
FILES on its own disk (data/purchase_docs/<reg>/...), because the platform
links they came from either expire within the hour (Motorway's signed
document CDN) or never existed as links at all (Carwow's zip bundle). A
file on the Mac's disk is nothing to Dealer OS, so before a push each one
not yet uploaded is sent, once, to Dealer OS's own file store:

    PUT {DEALER_OS_BASE_URL}/api/bidbrain/documents?reg=<REG>&name=<basename>
    Authorization: Bearer <token>, Content-Type: <the file's own type>,
    body: the raw bytes

and the reply {"url": "https://..."} is the lasting link, remembered here
against the local path (dealer_os_files table) so the same file is never
sent twice. The push then carries that link in place of the local path.
Until Dealer OS has that route (a 404), nothing is uploaded and the push
simply carries no link for that file, as today. Photos are not handled
here: a car's photo_urls are already lasting CDN links.
é    N)Údbé–   é   i 	= iÐ  )Úservice_history_photosÚ	v5_photosÚdocument_pathsÚv5_local_pathsÚ
photo_urls)Ú	photo_url)ú.jpgz.jpegz.pngz.heicz.webpz.tifz.tiffc                   ó   • \ rS rSrSrSrSrg)ÚDocumentUploadErroré.   zmAn upload that did not land, in plain words. missing_route is True
when Dealer OS has no documents route yet.F© N)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Úmissing_routeÚ__static_attributes__r   ó    Ú9/Users/stevendouglas/BidBrain/bidbrain/dealer_os_files.pyr   r   .   s   † ñ2àƒMr   r   c                 ó&   • U R                  S5        g )NzCREATE TABLE IF NOT EXISTS dealer_os_files (
        local_path TEXT PRIMARY KEY, url TEXT NOT NULL, uploaded_at TEXT NOT NULL))Úexecute)Úconns    r   Ú_ensurer   4   s   € Ø‡L�Lð Võ Wr   ÚpathÚreturnc                 ó  • [         R                  " U =(       d    [         R                  5      n [        U5        UR	                  S5       Vs0 s H  o"S   US   _M     snUR                  5         $ s  snf ! UR                  5         f = f)z2{local_path: url} for every file already uploaded.z+SELECT local_path, url FROM dealer_os_filesr   é   )r   ÚconnectÚDB_PATHr   r   Úclose)r   r   Úrs      r   Ú
known_urlsr&   9   sg   € ä�:Š:�d×(œbŸj™jÓ)€DðÜ�ŒØ$(§L¡LÐ1^Ô$_Ó`Ò$_˜q�!‘�a˜‘d’
Ñ$_Ñ`à�
‰
�ùò aøà�
‰
�ús   ¯A7 ÁA2ÁA7 Á2A7 Á7B	c                 ó.  • [         R                  " U=(       d    [         R                  5      n [        U5        UR	                  SX[         R
                  " 5       45        UR                  5         UR                  5         g ! UR                  5         f = f)NzTINSERT OR REPLACE INTO dealer_os_files (local_path, url, uploaded_at) VALUES (?,?,?))r   r"   r#   r   r   Ú_utcnowÚcommitr$   )Ú
local_pathÚurlr   r   s       r   Úrememberr,   C   s^   € Ü�:Š:�d×(œbŸj™jÓ)€DðÜ�ŒØ�‰ÐkØ ¤r§z¢z£|Ð4ô	6à�‰Œà�
‰
�øˆ�
‰
�ús   ¯AB ÂBc                 óR   • [        U [        5      =(       a    U R                  S5      $ )NÚ/)Ú
isinstanceÚstrÚ
startswith)Úvalues    r   Úis_localr3   N   s   € Ü�eœSÓ!×; e×&6Ñ&6°sÓ&;Ð;r   c                 óò   • SSK nSSKnUR                  SS9nUR                  SSSSSS	S
S[	        [
        5      U SU/SSS9nUR                  S:w  d$  [        R                  R                  U5      (       d  gU$ )z‡A JPEG copy of an image with a 2000 px longer edge, via macOS's own
sips, in a temp file the caller removes. None if sips cannot do it.r   Nr   )ÚsuffixÚsipsz-sÚformatÚjpegÚformatOptionsÚ80z-Zz--outT)Úcapture_outputÚtext)
Ú
subprocessÚtempfileÚmktempÚrunr0   ÚDOWNSIZE_EDGEÚ
returncodeÚosr   Úexists)r*   r=   r>   Úoutr%   s        r   Ú
_downsizedrF   R   s~   € ó ÛØ
�/‰/ ˆ/Ð
(€CØ�‰˜  h°¸¸oÈtØœc¤-Ó0°*¸gÀsðLà&*°ð 	ð 	7€Að 	‡|�|�qÓ¤§¡§¡¨s× 3Ñ 3ØØ€Jr   c                 óØ  • S[         l        U nSn[        R                  R	                  U 5      n[        R                  R                  U 5      S   R                  5       n	U[        :”  aŸ  U	[        ;   a  [        U 5      nU(       a'  [        R                  R	                  U5      [        :”  aO  U(       a  [        R                  " U5        [        [        R                  R                  U 5       SUS-  S S35      eUnS[         l         [        US	5       n
U
R                  5       nSSS5        U(       a  [        R                  " U5        [        R                   " U5      S
   =(       d    SnUS;  a.  [        [        R                  R                  U 5       SU S35      e[        R                  R                  U 5      n[         R                  (       a%  [        R                  R                  U5      S
   S-   n["        R$                  R'                  U=(       d    SUS.5      n["        R(                  R+                  UR-                  S5      S-   U-   WSUSU-   S.S9nU=(       d    ["        R(                  R.                  n U" XôS9 n[0        R2                  " UR                  5       R5                  S5      =(       d    S5      nSSS5        [C        W[D        5      (       a  URG                  S 5      OSnU(       d  [        S!5      eU$ ! , (       d  f       GNì= f! U(       a  [        R                  " U5        f f = f! , (       d  f       N= f! ["        R6                  R8                   aD  nUR:                  S;   a  [        S5      nSUl        UUe[        SUR:                   S35      UeSnAf["        R6                  R>                  [@        4 a  n[        SU 35      UeSnAff = f)"z†Send one file, return its lasting url. Raises DocumentUploadError.
Sets upload.downsized to True when the file had to be shrunk first.FNr!   z is g    €„.Az.1fz/ MB and could not be made small enough to send.TÚrbr   zapplication/octet-stream)z
image/jpegz	image/pngzapplication/pdfz is a z% file, which Dealer OS does not take.r   Ú )ÚregÚnamer.   z/api/bidbrain/documents?ÚPUTzBearer )zContent-TypeÚAuthorization)ÚdataÚmethodÚheaders)Útimeoutzutf-8z{})i‘  i“  i”  z6Dealer OS has no documents route open to this Mac yet.zDealer OS answered z to a document upload.z.Could not reach Dealer OS to send a document: r+   z.Dealer OS took the file but sent no link back.)$ÚuploadÚ	downsizedrC   r   ÚgetsizeÚsplitextÚlowerÚMAX_FILE_BYTESÚ	IMAGE_EXTrF   Úremover   ÚbasenameÚopenÚreadÚ	mimetypesÚ
guess_typeÚurllibÚparseÚ	urlencodeÚrequestÚRequestÚrstripÚurlopenÚjsonÚloadsÚdecodeÚerrorÚ	HTTPErrorÚcoder   ÚURLErrorÚOSErrorr/   ÚdictÚget)r*   rJ   Úbase_urlÚapi_keyrQ   ÚopenerÚ	send_pathÚtmpÚsizeÚextÚfÚbodyÚctyperK   ÚqÚreqÚdo_openr%   ÚreplyÚeÚerrr+   s                         r   rR   rR   `   s(  € ð „FÔØ€IØ
€CÜ�7‰7�?‰?˜:Ó&€DÜ
�'‰'×
Ñ
˜:Ó
& qÑ
)×
/Ñ
/Ó
1€CØŒnÓØ”)ÓÜ˜ZÓ(ˆCÞ”b—g‘g—o‘o cÓ*¬^Ó;ÞÜ—	’	˜#”Ü%¬¯©×)9Ñ)9¸*Ó)EÐ(FÀdÈ4ÐRUÉ:ÐVYÐJZð  [Jð  'Kó  Lð  LØˆ	ØŒÔðÜ�)˜TÔ" aØ—6‘6“8ˆD÷ #ö Ü�IŠI�cŒNÜ× Ò  Ó+¨AÑ.×LÐ2L€EØÐBÓBÜ!¤R§W¡W×%5Ñ%5°jÓ%AÐ$BÀ&ÈÈÐOtÐ"uÓvÐvÜ�7‰7×Ñ˜JÓ'€DÜ××Ü�w‰w×Ñ Ó% aÑ(¨6Ñ1ˆÜ�‰×Ñ s§y¨b¸$Ñ?Ó@€AÜ
�.‰.×
 Ñ
 Ø�‰˜ÓÐ9Ñ9¸AÑ=ÀDÐQVØ!&¸ÀWÑ9LÑMð !ð O€Cð ×.œŸ™×.Ñ.€Gð_Ù�SÒ*¨aÜ—J’J˜qŸv™v›xŸ™¨wÓ7×?¸4Ó@ˆE÷ +ô )¨´×5Ñ5ˆ%�)‰)�EÔ
¸4€CÞÜ!Ð"RÓSÐSØ€J÷E #Ö"ûö Ü�IŠI�c�Nð ú÷ +Õ*ûä�<‰<×!Ñ!ó 	_Ø�6‰6�_Ó$ô
 &Ð&^Ó_ˆCØ $ˆCÔØ˜1ÐÜ!Ð$7¸¿¹°xÐ?UÐ"VÓWÐ]^Ð^ûÜ�L‰L×!Ñ!¤7Ð+ó _Ü!Ð$RÐSTÐRUÐ"VÓWÐ]^Ð^ûð_úsf   Ä	L ÄLÄ&L ÊM Ê
=M ËM Ì
LÌL Ì L=Í 
MÍ
M ÍM ÍO)Í/?N.Î.'O)ÏO$Ï$O)c           
      óª  • [        U5      nS=n=n=pšSnSnU  GH9  n/ n[         H,  nUR                  UR                  U5      =(       d    / 5        M.     [         H;  nUR                  U5      (       d  M  UR                  UR                  U5      5        M=     U H²  n[        U5      (       a  UU;   a  M  [        R                  R                  U5      (       d  US-  nMF  Ub  Xu:¼  a
  XxXšSS.s  s  $  [        UUR                  S5      =(       d    SXUS9n[        UUU5        UUU'   US-  nSn[        R                   (       d  M­  U
S-  n
M´     GM<     XxXšUS.$ ! [         ak  nUR                  (       a  XxXš[        U5      S.s SnAs  s  $ U	S-  n	US-  n[        U5      nU[        :¼  a  S	U S
U 3nXxXšUS.s SnAs  s  $  SnAGM1  SnAff = f)zÆUpload every local document in these purchase rows that has no
lasting link yet, up to limit per call. Returns counts. Stops at the
first missing route or refused token (nothing else would succeed).r   rI   r!   Nzmore next push)ÚsentÚskippedÚfailedrS   ÚnoterJ   )rr   zstopped after z> failures in a row, Dealer OS is not storing files right now: )r&   ÚLOCAL_LIST_KEYSÚextendro   ÚLOCAL_SINGLE_KEYSÚappendr3   rC   r   rD   rR   r   r   r0   ÚMAX_CONSECUTIVE_FAILURESr,   rS   )Ú	purchasesrp   rq   r   rr   ÚlimitÚhaver�   r‚   rƒ   rS   Ústreakr„   r%   ÚtodoÚkeyÚpr+   r~   s                      r   Úsync_documentsr‘   —   sÆ  € ô �dÓ€DØ*+Ð+€DÐ+ˆ7Ð+�VØ€FØ€DÜˆØˆß"ˆCØ�K‰K˜Ÿ™˜c›
×( bÖ)ñ #ç$ˆCØ�u‰u�S�z‹zØ—‘˜AŸE™E #›JÖ'ñ %ó ˆAÜ˜A—;‘; ! t£)ÙÜ—7‘7—>‘> !×$Ñ$Ø˜1‘�ÙØÑ  T£]Ø $ÀFÐl|Ñ}Ô}ðÜ˜Q §¡ e£× 2°°HÈfÑU�ô �Q˜˜TÔ"ØˆD�‰GØ�A‰IˆDØˆFÜ××ÑØ˜Q‘’	ô3 ñ ðB ¸Ð`dÑeÐeøô! 'ó 	Ø—?—?Ø$(ÈÔpsÐtuÓpvÑw×wÑwØ˜!‘�Ø˜!‘�Ü˜1“v�ØÔ5Ó5Ø+¨F¨8Ð3qÐrsÐqtÐu�DØ$(ÈÐptÑu×uÑuÞûð	ús*   Ã5$EÅ
GÅ'GÆGÆ,GÆ<GÇGc                 ó  • [        U5      n/ nU  Hì  n[        U5      n[         H€  nUR                  U5      n[	        U[
        5      (       d  M+  U Vs/ s H&  n[        U5      (       a  UR                  U5      OUPM(     snXV'   XV    Vs/ s H  oˆ(       d  M  UPM     snXV'   M‚     [         H9  nUR                  U5      n[        U5      (       d  M&  UR                  U5      XV'   M;     UR                  U5        Mî     U$ s  snf s  snf )z¤Copies of the rows with each local document path replaced by its
lasting link, or dropped when none exists yet. Non local entries (a
CDN link) are kept as they are.)	r&   rn   r…   ro   r/   Úlistr3   r‡   rˆ   )	rŠ   r   rŒ   rE   r%   Údr�   ÚvalsÚvs	            r   Ú
with_linksr—   Ã   sà   € ô �dÓ€DØ
€CÛˆÜ�‹Gˆß"ˆCØ—5‘5˜“:ˆDÜ˜d¤D×)Ñ)ÙÙAEÓFÂ¸A¤X¨a§[¡[�d—h‘h˜q”k°aÒ7ÁÑFˆA‰FØ!"¢Ó-¢˜A«1—a¡Ñ-ˆA‹Fñ #÷ %ˆCØ—‘�c“
ˆAÜ˜�{‹{ØŸ™ !›�“ñ %ð 	�
‰
�1Žñ ð €Jùò GùÚ-s   Á-DÂ
DÂD)N)é<   N) r   rf   r]   rC   Úsqlite3Úurllib.errorr_   Úurllib.parseÚurllib.requestÚbidbrainr   ÚMAX_PER_PUSHr‰   rW   rA   r…   r‡   rX   ÚRuntimeErrorr   r   r0   rn   r&   r,   Úboolr3   rF   rR   r‘   r“   r—   r   r   r   Ú<module>r¡      sÙ   ðñó* Û Û 	Û Û Û Û å à€ð Ð ð €Ø€Øk€Ø"Ð ØH€	ô˜,ô òWñ
�Sð  Dõ ñ Cð °4õ ð<�tô <òñ4È3õ 4ðn >BÈ$ÐVbñ )f°sð )fÐgkõ )fñX ð ¨tö r   