ó
    ÒQœj1  ã                  ó<  • % S r SSKJr  SSKrSSKrSSKJr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  \" 5       R$                  R&                  rS
\S'   \S-  rS
\S'   SS jrSS jrSS jrSS.         SS jjrSS jrSS jr\S:X  a  \" 5         gg)a#  Where a document's files live.

Every file the playground keeps for a document is named by the document's
storage key and sits in the local data directory: the served PDF, the parse
and figure artifacts the ingest read, and the upload workdir. On the
developer host that directory is the only copy.

Hosted, the local directory is scratch that a task replacement erases, and
the durable copy is an ``s3://`` prefix from settings holding one
``<doc_key>/`` prefix per document with the same file names inside it. Two
movements keep the two in step: ``publish`` copies files up after every
stage of an upload, and ``local_pdf`` fetches the served PDF back down when
a task no longer has it. Nothing here ever deletes from the prefix; removing
a document from the library removes its database rows and local files only,
so a later upload of the same bytes finds every artifact still there.

Run as a module, it publishes the whole current library once, which is how
the host's documents were moved into the bucket:

    uv run python -m quber.playground.storage
é    )ÚannotationsN)ÚdatetimeÚtimezone)ÚPath)ÚIterableÚOptional)ÚS3Path)Úlogger)Úget_settingsr   ÚDATA_DIRÚuploadsÚUPLOADSc                ó”   • [        5       R                  R                  nU(       d  g[        UR	                  S5      S-   U -   S-   5      $ )zCThe document's prefix in the bucket, or None on the developer host.NÚ/)r   Ú
playgroundÚartifacts_urir	   Úrstrip)Údoc_keyÚroots     Ú6/home/mande/repo/quber/src/quber/playground/storage.pyÚprefixr   (   s>   € ä‹>×$Ñ$×2Ñ2€DÞØÜ�$—+‘+˜cÓ" SÑ(¨7Ñ2°SÑ8Ó9Ð9ó    c                óè   • [        U 5      nUc  gSnU H;  nUR                  5       (       d  M  X$R                  -  R                  USS9  US-  nM=     U(       a  [        R
                  " SX0U5        U$ )z­Copy the given local files under the document's prefix. Returns how
many were copied; zero, without touching anything, on the developer host
or when none of the paths exist.r   T)Úforce_overwrite_to_cloudé   z!published {} file(s) for {} to {})r   Úis_fileÚnameÚupload_fromr
   Úinfo)r   ÚpathsÚdestÚcopiedÚpaths        r   Úpublishr$   0   sp   € ô �'‹?€DØ�|ØØ€FÛˆØ�|‰|�~‰~ÙØ	—	‘	Ñ	×&Ñ& tÀdÐ&ÑKØ�!‰Šñ	 ö
 Ü�ŠÐ7¸È$ÔOØ€Mr   c                óŠ   ^ • UR                  5       (       d  g[        T [        U 4S jUR                  5        5       5      5      $ )z:Copy every file the upload workdir holds for the document.r   c              3  ój   >#   • U  H(  oR                   R                  T5      (       d  M$  Uv •  M*     g 7f)N)r   Ú
startswith)Ú.0Úpr   s     €r   Ú	<genexpr>Ú"publish_workdir.<locals>.<genexpr>F   s%   øé € Ð"^Ò.?¨Ç6Á6×CTÑCTÐU\×C]§1¡1Ò.?ùs   ƒ#3ª	3)Úis_dirr$   ÚsortedÚiterdir)r   Úworkdirs   ` r   Úpublish_workdirr0   B   s2   ø€ à�>‰>×ÑØÜ�7œFÔ"^¨g¯o©oÔ.?Ó"^Ó^Ó_Ð_r   )Ú
source_uric               ó  • U UUU[         R                  " [        R                  5      R	                  SS9S.n[
        U  S3-  nUR                  R                  SSS9  UR                  [        R                  " USS95        U$ )	zÑRecord where the document came from beside its PDF: the name it was
uploaded under, its full hash, the S3 object it was taken from if any, and
when. The key carries none of this, so the prefix says it instead.Úseconds)Útimespec)r   ÚfilenameÚsha256r1   Úuploaded_atú.source.jsonT©ÚparentsÚexist_oké   )Úindent)r   Únowr   ÚutcÚ	isoformatr   ÚparentÚmkdirÚ
write_textÚjsonÚdumps)r   r5   Úcontent_hashr1   Úmanifestr#   s         r   Úwrite_source_manifestrH   I   s{   € ð ØØØ Ü—|’|¤H§L¡LÓ1×;Ñ;ÀYÐ;ÐOñ€Hô ˜�y Ð-Ñ-€DØ‡K�K×Ñ˜d¨TÐÑ2Ø‡O�O”D—J’J˜x°Ñ2Ô3Ø€Kr   c                ó€  • [         U  S3-  [        U  S3-  4 H  nUR                  5       (       d  M  Us  $    [        U 5      nUc  gX  S3-  nUR	                  5       (       d  g[         U  S3-  nUR
                  R                  SSS9  [        R                  " SUR                  U5        UR                  U5        U$ )zuThe served PDF as a local path, fetched from the bucket when this host
no longer has it. None when it exists nowhere.z.pdfNTr9   zfetching {} from {})r   r   r   r   ÚexistsrA   rB   r
   r   r   Údownload_to)r   Ú	candidateÚremoteÚsourceÚtargets        r   Ú	local_pdfrP   \   sÁ   € ô  G 9¨DÐ!1Ñ1´7À¸yÈÐ=MÑ3MÓNˆ	Ø×Ñ×ÓØÒñ Oô �G‹_€FØ�~ØØ˜	 Ð&Ñ&€FØ�=‰=�?‰?ØÜ˜7˜) 4Ð(Ñ(€FØ
‡M�M×Ñ ¨tÐÑ4Ü
‡K‚KÐ% v§{¡{°FÔ;Ø
×Ñ�vÔØ€Mr   c            	     ól  ^• SSK Jn   [        S5      c  [        R                  " S5        U R                  5        nUR                  S5      R                  5       nSSS5        SnW H™  u  mpE[        [        R                  T S35      5      n[        U4S jU 5       5      (       d  UR                  [        TXES	95        [        TU5      [        T[         T S
3-  5      -   nX7-  n["        R$                  " STU5        M›     ["        R&                  " SU[)        U5      5        g! , (       d  f       NÑ= f)z¯Publish every document the database knows about from the local data
directory: the served PDF and the artifacts beside it, plus the upload
workdir when this host still has it.r   )ÚdbÚprobeNz@QUBER_PLAYGROUND_ARTIFACTS_URI is not set; nothing to publish tozPSELECT doc_key, filename, content_hash FROM ade_playground.documents ORDER BY idz.*c              3  óF   >#   • U  H  oR                   T S 3:H  v •  M     g7f)r8   N)r   )r(   Úfr   s     €r   r*   Ú"publish_library.<locals>.<genexpr>~   s   øé € ÐEºu¸!—6‘6 ˜y¨Ð5Ö5ºuùs   ƒ!)r5   rF   z
-artifactsz{}: {} file(s)z'published {} file(s) for {} document(s))Úquber.playgroundrR   r   ÚsysÚexitÚconnectÚexecuteÚfetchallr-   r   ÚglobÚanyÚappendrH   r$   r0   r   r
   r   ÚsuccessÚlen)	rR   ÚconnÚrowsÚtotalr5   rF   Úfilesr"   r   s	           @r   Úpublish_libraryrf   o   sü   ø€ õ $äˆgƒÑÜ�ŠÐSÔTØ	�‰Œ˜Ø�|‰|Ø^ó
ç
‰(‹*ð 	÷ 
ð €EÛ+/Ñ'ˆ�Ü”x—}‘}¨ y° ^Ó4Ó5ˆÜÔE¹uÓE×EÑEØ�L‰LÔ.¨wÀÑeÔfÜ˜ %Ó(¬?¸7ÄGÐQXÐPYÐYcÐNdÑDdÓ+eÑeˆØ‰ˆÜ�ŠÐ$ g¨vÖ6ñ ,0ô ‡N‚NÐ<¸eÄSÈÃYÕO÷ 
�ús   º D%Ä%
D3Ú__main__)r   ÚstrÚreturnzOptional[S3Path])r   rh   r    zIterable[Path]ri   Úint)r   rh   r/   r   ri   rj   )
r   rh   r5   rh   rF   rh   r1   zOptional[str]ri   r   )r   rh   ri   zOptional[Path])ri   ÚNone)Ú__doc__Ú
__future__r   rD   rX   r   r   Úpathlibr   Útypingr   r   Úcloudpathlibr	   Úlogurur
   Úquber.settingsr   r   Údata_dirr   Ú__annotations__r   r   r$   r0   rH   rP   rf   Ú__name__© r   r   Ú<module>rw      s²   ðòõ, #ã Û 
ß 'Ý ß %å Ý å 'á“×*Ñ*×3Ñ3€ˆ$Ó 3Ø˜9Ñ$€ˆÓ $ô:ôô$`ð TXñØðØ"ðØ25ðØCPðà	õô&ô&Pð. ˆzÓÙÕð r   