
    i              	       l   d Z ddlZddlZddlZddlZddlZddlmZmZ  e	ej                  j                  dd            Zej                  j                  dd      ej                  j                  dd      d	Zd
ededefdZej!                         D  ci c]  \  } }| |r	 e| |      nd c}} Zej!                         D ]8  \  ZZer	ej(                  j+                  dej-                          de d       : dedefdZdeddfdZdefdZ G d de      Zd Zedk(  r e        yyc c}} w )aQ  
LableBrain static file server + per-artist annotations API.

Public reads, gated writes:
  - GET  /<name>                   -> tries /<name>.html (clean URLs)
  - GET  /<anything>               -> static file (public, no auth)
  - GET  /annotations/<artist>     -> annotations list for artist (public)
  - POST /annotations/<artist>     -> requires Basic Auth <artist>:<password>

Environment variables:
  PORT              TCP port to listen on (default 8080)
  RUBY_PASSWORD     password required to POST /annotations/ruby
  ORLANDO_PASSWORD  password required to POST /annotations/orlando
    N)SimpleHTTPRequestHandlerThreadingHTTPServerPORT8080RUBY_PASSWORD ORLANDO_PASSWORD)rubyorlandouserpwreturnc                 x    dt        j                  |  d| j                  d            j                  d      z   S )NzBasic :utf-8ascii)base64	b64encodeencodedecode)r   r   s     /opt/lablebrain/server.py_basicr       s8    f&&$q~'<'<W'EFMMgVVV    z[srv] WARNING: z'_PASSWORD not set -- POST /annotations/z will be refused.
artistc                 H    t         j                  j                  d|  d      S )Nzannotations-z.json)ospathabspath)r   s    r   ann_pathr   1   s    77??\&788r   r   c                 .   t         j                  j                  |       xs d}t        j                  |d       t	        j
                  |dd      \  }}	 t        j                  |d      5 }t        j                  ||d	       |j                          t        j                  |j                                d d d        t        j                  ||        y # 1 sw Y    xY w# t        $ r' 	 t        j                  |        # t        $ r Y  w xY ww xY w)
N.T)exist_okz.ann-z.tmp)dirprefixsuffixw   )indent)r   r   dirnamemakedirstempfilemkstempfdopenjsondumpflushfsyncfilenoreplace	ExceptionunlinkOSError)r   datadfdtmpfs         r   safe_write_jsonr<   5   s    
$AKKD!1WVDGBYYr3 	!1IIdAa(GGIHHQXXZ 	! 	

3		! 	!
  	IIcN 	  		sI   C$ -AC9C$ C!C$ $	D.DD	DDDDc                     	 t        | d      5 }t        j                  |      cd d d        S # 1 sw Y   y xY w# t        t        j                  f$ r |cY S w xY w)Nr)openr.   loadFileNotFoundErrorJSONDecodeError)r   defaultr;   s      r   safe_read_jsonrD   G   sP    $_ 	 99Q<	  	  	 t334 s#   9 -	9 69 9 AAc                   j     e Zd Zd ZdeddfdZdefdZddedefd	Zd
efdZ	d Z
 fdZd Z xZS )Handlerc                 T    t         j                  j                  d||z  z   dz          y )Nz[srv] 
)sysstderrwrite)selffmtargss      r   log_messagezHandler.log_messageP   s"    

C$J.56r   coder   Nc                 T   t        j                  |      j                  d      }| j                  |       | j	                  dd       | j	                  dt        t        |                   | j	                  dd       | j                          | j                  j                  |       y )Nr   Content-Typezapplication/jsonContent-LengthzCache-Controlzno-store)
r.   dumpsr   send_responsesend_headerstrlenend_headerswfilerK   )rL   rP   payloadbodys       r   _jsonzHandler._jsonS   s    zz'"))'24 );<)3s4y>:*5

r   r   c                     | j                  d       | j                  dd| d       | j                  dd       | j                          | j                  j	                  d       y )Ni  zWWW-AuthenticatezBasic realm="LableBrain-"rR   z
text/plains   Authentication required)rU   rV   rY   rZ   rK   )rL   r   s     r   	_send_401zHandler._send_401\   s^    3&vha0	
 	6

34r   locationc                     | j                  |       | j                  d|       | j                  dd       | j                          y )NLocationrS   0)rU   rV   rY   )rL   ra   rP   s      r   	_redirectzHandler._redirectf   s=    4 X.)3/r   r   c                     |j                  dd      d   j                  d      }|j                  d      sy |t        d      d  j                  d      j	                         }|t
        v r|S y )N?   r   /zannotations/)splitstrip
startswithrX   lowerARTIST_PASSWORDS)rL   r   pr   s       r   _match_annotationszHandler._match_annotationsl   sh    JJsAq!'',||N+3~&'(..s399;%%Mr   c                    | j                   }d|v r|j                  dd      \  }}d|z   }n|d}}|j                  d      ry|j                  d      r!|dd }|dk(  ry| j	                  ||z   d	       y
|j                  dd      d   }|dv sd|v ry|j                  d      dz   }t        j                   j                  t        j                         |      }t        j                   j                  |      rd|z   |z   | _         yy)a*  
        Clean-URL support:
          - /foo.html  -> 301 redirect to /foo (canonical)
          - /foo       -> internally rewrite to /foo.html if foo.html exists
        Only applies to .html; assets with other extensions pass through.
        Skips /annotations/* because that's an API.
        rg   rh   r   z/annotations/Fz.htmlN-  Tri   )r   ri   r!   )r   rj   rl   endswithre   rsplitlstripr   joingetcwdisfile)rL   rawr   qscleanbasenamecandidate_relcandidate_abss           r   _maybe_rewrite_clean_urlz Handler._maybe_rewrite_clean_urlu   s     ii#:yya(HD"rBB"D???+ ==!"IE{NN52:s+ ;;sA&r*9xC(72RYY[-@77>>-(m+b0DIr   c                     | j                  | j                        }|'t        t        |      g       }| j	                  d|      S | j                         ry t        |          S )N   )rp   r   rD   r   r]   r   superdo_GET)rL   r   r7   	__class__s      r   r   zHandler.do_GET   s[    ((3!(6"2B7D::c4((((*w~r   c                 .   | j                  | j                        }|| j                  dddi      S t        j	                  |      }|s| j                  dd| di      S | j
                  j	                  dd      }||k7  r| j                  |      S t        | j
                  j	                  dd	      xs d	      }|d
k  s|dkD  r| j                  dddi      S | j                  j                  |      }	 t        j                  |j                  d            }t        |t              s| j                  dddi      S g }|D ]  }t        |t               s|j#                  t%        |j	                  dd            d d t%        |j	                  dd            d d |j	                  d      xs g D 	cg c]  }	t%        |	      d d  c}	d d t%        |j	                  dd            d d t%        |j	                  dd            d d d        	 t'        t)        |      |       | j                  ddt+        |      d      S # t        $ r | j                  dddi      cY S w xY wc c}	w # t        $ r!}
| j                  ddd|
 i      cY d }
~
S d }
~
ww xY w) Ni  errorz	not foundi  z password not configuredAuthorizationr   rS   rd   r   i@B i  z
bad lengthr   zbad jsonzpayload must be a listid@   	timestamp(   tags
   notei  created)r   r   r   r   r   i  zwrite failed: r   T)okcount)rp   r   r]   EXPECTED_BASICgetheadersr`   intrfilereadr.   loadsr   r4   
isinstancelistdictappendrW   r<   r   rX   )rL   r   expectedheaderlengthr{   r[   cleaneditemtes              r   do_POSTzHandler.do_POST   sz   ((3>::cG[#9::!%%f-::cGx7O-P#QRR!!/26X>>&))T\\%%&6<CDQ;&9,::cG\#:;;jjoof%	:jjG!45G '4(::cG-E#FGG 		DdD)NN $!34Sb9 +r!:;CR@48HHV4D4JLac!fSbkLSbQ &"!56u= )R!89#2> 			DHV,g6 zz#dS\BCC+  	:::cGZ#899	: M  	D::cG~aS-A#BCC	Ds6   5$I 1I%I* I"!I"*	J3J	JJ)rs   )__name__
__module____qualname__rO   r   r]   rW   r`   re   rp   r   r   r   __classcell__)r   s   @r   rF   rF   O   sW    7# 4 5 5# S s $L	 (Dr   rF   c                      t        dt        ft              } t        j                  j                  dt         dt        j                          d       	 | j                          y # t        $ r Y y w xY w)Nz0.0.0.0z[srv] listening on :z (cwd=z)
)
r   r   rF   rI   rJ   rK   r   ry   serve_foreverKeyboardInterrupt)srvs    r   mainr      s^    
y$/
9CJJ+D6		}CHI s   A% %	A10A1__main__)__doc__r   r.   r   rI   r+   http.serverr   r   r   environr   r   rn   rW   r   itemsr   r   r   rJ   rK   upperr   r<   rD   rF   r   r   )r   r   s   00r   <module>r      ss     	 
  E 2::>>&&)* zz~~or2zz~~0"5 W W# W# W ',,. 2VFB4/
 #((* 
JFB

flln- .!!'(;=	

9S 9S 9#  $ D& DD zF ms   D0