From 7f54768db2668e672f95e3c32c6163f616fdb171 Mon Sep 17 00:00:00 2001 From: 153957 <153957@users.noreply.github.com> Date: Mon, 2 Sep 2024 06:06:27 +0000 Subject: [PATCH] deploy: bdfd087a480fc87ef73dad5074f0e25a035675f8 --- .buildinfo | 4 + .doctrees/environment.pickle | Bin 0 -> 86902 bytes .doctrees/index.doctree | Bin 0 -> 5148 bytes .doctrees/introduction.doctree | Bin 0 -> 4117 bytes .doctrees/writer.doctree | Bin 0 -> 99003 bytes .doctrees/wsgi.doctree | Bin 0 -> 23887 bytes .doctrees/xml-rpc-server.doctree | Bin 0 -> 5920 bytes .nojekyll | 0 .../examples/datastore_config_server.html | 154 +++ _modules/index.html | 86 ++ _modules/writer/storage.html | 471 +++++++++ _modules/writer/store_events.html | 230 +++++ _modules/writer/writer_app.html | 196 ++++ _modules/wsgi/wsgi_app.html | 299 ++++++ _sources/index.rst.txt | 25 + _sources/introduction.rst.txt | 11 + _sources/writer.rst.txt | 37 + _sources/wsgi.rst.txt | 22 + _sources/xml-rpc-server.rst.txt | 6 + _static/basic.css | 925 ++++++++++++++++++ _static/doctools.js | 156 +++ _static/documentation_options.js | 13 + _static/favicon.ico | Bin 0 -> 4286 bytes _static/file.png | Bin 0 -> 286 bytes _static/header.png | Bin 0 -> 3210 bytes _static/hisparc_style.css | 70 ++ _static/language_data.js | 199 ++++ _static/minus.png | Bin 0 -> 90 bytes _static/nature.css | 252 +++++ _static/plus.png | Bin 0 -> 90 bytes _static/pygments.css | 75 ++ _static/searchtools.js | 620 ++++++++++++ _static/sphinx_highlight.js | 154 +++ genindex.html | 374 +++++++ index.html | 141 +++ introduction.html | 125 +++ objects.inv | Bin 0 -> 881 bytes py-modindex.html | 152 +++ search.html | 103 ++ searchindex.js | 1 + writer.html | 505 ++++++++++ wsgi.html | 249 +++++ xml-rpc-server.html | 125 +++ 43 files changed, 5780 insertions(+) create mode 100644 .buildinfo create mode 100644 .doctrees/environment.pickle create mode 100644 .doctrees/index.doctree create mode 100644 .doctrees/introduction.doctree create mode 100644 .doctrees/writer.doctree create mode 100644 .doctrees/wsgi.doctree create mode 100644 .doctrees/xml-rpc-server.doctree create mode 100644 .nojekyll create mode 100644 _modules/examples/datastore_config_server.html create mode 100644 _modules/index.html create mode 100644 _modules/writer/storage.html create mode 100644 _modules/writer/store_events.html create mode 100644 _modules/writer/writer_app.html create mode 100644 _modules/wsgi/wsgi_app.html create mode 100644 _sources/index.rst.txt create mode 100644 _sources/introduction.rst.txt create mode 100644 _sources/writer.rst.txt create mode 100644 _sources/wsgi.rst.txt create mode 100644 _sources/xml-rpc-server.rst.txt create mode 100644 _static/basic.css create mode 100644 _static/doctools.js create mode 100644 _static/documentation_options.js create mode 100644 _static/favicon.ico create mode 100644 _static/file.png create mode 100644 _static/header.png create mode 100644 _static/hisparc_style.css create mode 100644 _static/language_data.js create mode 100644 _static/minus.png create mode 100644 _static/nature.css create mode 100644 _static/plus.png create mode 100644 _static/pygments.css create mode 100644 _static/searchtools.js create mode 100644 _static/sphinx_highlight.js create mode 100644 genindex.html create mode 100644 index.html create mode 100644 introduction.html create mode 100644 objects.inv create mode 100644 py-modindex.html create mode 100644 search.html create mode 100644 searchindex.js create mode 100644 writer.html create mode 100644 wsgi.html create mode 100644 xml-rpc-server.html diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..44a0dee --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: ab81b1b815cedb7ca1fdb7811bd4966f +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..21f9f4a908a7ae61b4aa272f78e953d14e5ebc36 GIT binary patch literal 86902 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 C6e3{& literal 0 HcmV?d00001 diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..73e2fa76c0834ce725c1875f7e7eb9299f529ec2 GIT binary patch literal 5148 zcmds5O>ZPe8MfD+vB#e~&h93wWJTwYtT%W(>+tOhik1Ut6@!*2fy2^U(_J%Fc6V2& ztJn){C{|GaQLM+y*KY4d}V*_`zrSW zmUt{=squ`E+HL9KN@>Imk0zc`-UM&)B6%W{(VjQRjVFz#h03GQW5$a(?y?rB#4wp) z5AJ138SjZvfyp?3;)OEffsvL5@9;6%>;Lwh9~Q&}+fEr^H-*^H17_El zF`0z@KpUdTk_L1TM3KHuhlS5XM+I?PBK1pI-PMG9qN=3y;jqVYo49* zBDX7sjc7$7*2FEbWIKM%nF6DB&B!nGg^GDU%aep>{WF!F^rtLqeyN}@laP=1Gi?gH z9LaK`9`d}&>2^j*CDK)f!N#N8ut?lcf`Bmd)%(ZY#j2`h~nWj z&>kCgIaN8+HM~r zyc|9m@x&4JNgb*?ur3<`CehFqKX-3gup24IJCl(=#_G!o9ov?P3mlD$lxw?owLYtu zz5aL4e*Lapc8Ay{Z9@1F{_WABe4aYL0a72&Q?99G+gJyV6yn=R)$|!7a~0wbb_F9d zNfB3lLtw-+wAZ{&Lmp zo`uNYKRqJikByr*ad-(h{Q0WGn>dj2pPr{&<3P%nao8*!NpnOcaS4Tg&7n|^XDF28 zF0!>-Kf~a7d2wOke}>_B5xZRVzGonDyoGh235@yyO(<@|VE?}A^d=Of{Lkkp*HDmh z1;s2+)N#KK4hw_13#h=-a`oQRUv$qOX}HXfWIN3yY`03SfV4By!<`S~bkG2%}A8f1e6kn#EOGR7p$Y6k*e7ThHh*KY>{WEEVUl-LluUb%5*)U7SQ33TUbAb0Mblx+cKKMsiiVk7*Geth3i%)(;kYfz0hIKxFK00%9Y7XvdJ^4FCa{^$ zlYm0;2xV7phP|)$w8Zx-7bkt5Wh(OpC>Eg%g4^S50dmTAY5B%3Ew@Wy@6652ZE-v# zPu(i_o!y~c{mR9OxyAN6y#d_Q0w#)Fhp`#4WR$ZJhs8r=#%m}R?F;DWR2KRuGEo5F z7^-MdvZ4zd;+e3B3~NG&w%ua6QT|M1uSw8tGtA@EpHdQrbpn*cJWE(a8*V#^a|l9z zCQ3EWL*=6^hSqiDXr;N|Tmc00i$ZE3R_z^dGWMs=Iema% z_MqqXx}Ko5SCJo)n-ATAL6a zJi!eFM;Ui+ZwAmh@j0r&$j1*n6nEXNyH&dju13Q$rk&d*o}3owOYCYHP2j#@BIOMF zwZkuqL)Wi&aVap%Al!mAXxJ#4+no#2veSncyA7uUDpcu50?1cLSbpycEz;w!!OxGs zOzGTqXpZVumnMWhca!J2xJ`|3D)yY7IL+;56GgZ0CQ92f)!?jBLe5(2l@Dl2h+$3& z)Z>AQro?c<8*{>`cIU#5o%&R_Xy?NI?q{n4UUYMQuWAO(C_+hq_MF;dyB;VsJg$RA zGFf?0$)Cb%9~O_`7U*E$VE9=@g9+t<(};3Gygn5>FxRVw7~~nzZ!>El1-&Xhl=b)i Fe*kEMx*z}m literal 0 HcmV?d00001 diff --git a/.doctrees/introduction.doctree b/.doctrees/introduction.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d591160ba93ac430830ff70eb28081fc0e414e8b GIT binary patch literal 4117 zcmb7H%Z?mH7Hzv-)m`1yPp}6GH&Yh!fQ74y1q);`@&X|NW_mF~vqMQ#W>iH?XJ#bv zu)7zQV1Z;yAvBU>Hf%>D{(;4Oj3xeK&dpb4l`(F&RO*ZyH{!m|eZ=>@fA1V_lz;xf z6hh|b6P6^oO1KGrJ5jOAcy7b9aOa2cIK1x%nmsbg>6iz<0ghP6G|@Z{-vqx;>{g~` zQlH;4JhoEh?Qzi)eX;dTcwY>B&q|x}&Xym3kz1`2SDhg~uwDC_pOgHx-5*1xPrqQ0 zZi6`TgQ!(kZ2Eq!<*C1x%AAK&H)DQ>S*zvDSy)HbY|6xt=38;fi~$kfy_{v$*LLEH z*%{q5GUKMKaWadDNGH~gSI9+p<{t`z|3L7BV}@_{4$Pz;&^A$tD! zMX?QT57*Jr5s`OA{4s=6*vw(`4Zl13y};6d^>=obw*2q)ZP|f(zh2fBVumd*$FA>JWiR+Et&abdeY6ovuRqZdN`iy9;{R%X7GG&f>0r(`uHRv}_tt zq18fjQ#I$FRhFgU!G_;yczA-Pqb_#E@5fG4>yO$-KWr#f!}r^0Q+OfqFG%h3cK zE4MsPg75!>r4F6GDP6v=oGqLUr{dt$4;CCvoS}6GEYSKmYFAcZv8$D`P?gW+IlMm& zez#Hq9VkBo!%n?)I>lWSvm4>d28Mt|fFIox^G?+BK*%bq{t z=Mr8--*%>9>iJ&6XKn#;`;VB;;YDz0&N5I22uIAxB+^gj@L7Z=pb~HeKX7(F{&;L8 zzHhE>MqF#9Bf;_{?@|qJ$Sf4#Y^n=G6CNmCs=&JR*|@ z5Tft*nX@WdlKfQ(xa}n_E20*XaBN9Hs&SpOlrB6dDJ9q``b$z_xJy)Yo)Mr9`FWuX zkGdyIl*BfAD2=Qjdk`hk&{zdSYAv^#R6*JeVT?g?lzE3s_iDDNoULdMzCm9QVz{P( z4zZ!z5`=sgEv{WYxZ;l}-+-Ka0U)L_lQ#VA@IAj@@M1Y3kTFA|%oud1)bMW!YYTI4 zItAHGiVG`LK2dssjJi&or2^oLFu5T-7)<>(=;PZTeguE+>v_C(JyDj||E0N}=2n92 z8P^lZ?YxwMvOg6zOCfB4@e^5;SP>p<`x9U?i`wLDN1VtMgpjBH&|Kt}okv10M2a8q zdo5K2{h`Si60IDf4IK4GxvrKG=4*zrm0Wug3(WX20$=4Dyj;2vr_Wpg5%iL(CakIq zJOz4GEb}=qP}hZHDip@e*4Si5*rnOIOh$V9oCQE8oACr0d;tvv=N2-q$KX1Pc*bNJ z;mt>N7wSFnNA;@>SWV|uO?&D$dH#^zP=33rCP-f(QRaz6ZrI2&z2K-$FzyIS0a+F= zL7*m~7t>=@yA^8nssb6e&D%wvN-7>?pl=be`WC;5=f)3+U@r=2_&A8)o*Hor|DTJY z@q67^m8bV&S-nCC9~FgCvf-=L>{2Q|D391?GnKaCFInGQBevXwl^X|*)bh~r%KfOW z+6Lsx_4T{z9W*%!T2A;-vNF+5|KuDs2aq(64=&_ g74mLNE~6xDj8S?hYO{ww>@W=tM7n!yCmzc%oy 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 CgF-q0 literal 0 HcmV?d00001 diff --git a/.doctrees/wsgi.doctree b/.doctrees/wsgi.doctree new file mode 100644 index 0000000000000000000000000000000000000000..785cce3c0a8386a3dd5467a789344428a945a62f GIT binary patch literal 23887 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 literal 0 HcmV?d00001 diff --git a/.doctrees/xml-rpc-server.doctree b/.doctrees/xml-rpc-server.doctree new file mode 100644 index 0000000000000000000000000000000000000000..125ba02f413e504fb2518f2e92d5668bbeae1ba2 GIT binary patch literal 5920 zcmd5=TaO$^72bY`hu|r!ZcvpC4K>`sOixBxG$jM^a7Vu)Vdb(?-%G)=p zs=c!yOD0kxuT*a-BmzSD1w0_$_#3<-BqV-Bc;!1)-E&)S*3JXTO0(*^pE~C|-#MqA zwSIr)8w>8wY^joqeAJ7QBrg)CtXWNpxXM_r?I(8cclN2>H60lpsiKlGvt|J^VlL7| zvfMthW|O9Ck*Z$3enqiZiy|K{=PlmmE063h@0gYrI%QKsX7g7cyx+Zl?@sptlTVnm zG!qjqJY*x1;MDj>*2~l`BS@ev-!`3aEHYmgFbgJN*L;QUP(6(H#V1JO`M zC7;rn`QZaW_YP`c1s+&|E&|vmDDpsa7L0Wg)Q0fDd;QC;Dn6Mv>hq) z9e$1f1lC_{l!{yZ)cYK7nZG{dLP1cLL1x8^DouhuYcBDgcyRCC`*&{8j2dapRf6DW zIye$(y2hhCfhOp}aat+OWROP3MWt1MkwEE)JT)lc0|0HJdO`3@&T=O(A{M9YE{6Y(ghfsV! zQZVD7kWd#sVz6v*6sbUpvSdjxkVWPcSsrT+TX^sSyWm8R+_hBnCDb1@Ea#0z1_Vbk zP)0J={QvR9iM}4ay1rJX^wnz+|4MAvR$vQ+67!A+V>ot$-MwH} zGNKe_6-ylJTO#3)0mTpfDdy{Bz`KhsC+IkZ&ujbAyonm+o*tO!^3PwS%T>~)SL7-k zd0L135ByV7*q+<>cIPa#BFf7~)JSkl0MYW2wf_nenjM{V%6?H<#-q6?E5o zBG?ges5M*7XBg!%FXYs5=P^kK`J9gAkZEi7LeCUxzd`U>i3(CgtP_dlpP4qkz{ki> zXA?RHslQL|;L`v$x%MccEDjkc^li-=X}#z_M#^kw0o(RcS#vIfK`e)Qkdj#)RB7r= z2N~BwUBn?wqLt5ZouVrr!e$&@FXDfJIzVVypH7%oDr7qnGOIZv-+rE5j=|V@T zy}Mx6nmH^%v!e4B&1PIlDh|VODY)MB_>$19))z;;yG?18D`1g|5xNs=+P{d>3T0J8 zV5W_nxvaE3;9Cc#Gh`_9A{2KP4N8-dNM|J)7iCdshzgb%S+lrr5u#k(9OB$VOjxU% zl}ypvAZMiVl13p)%bUmS46sdJXf`$Y+-y)`omW{nLNb}z=Vp^irV_F4kO|8=2eU@sAc4kmmS(Co(jvug(t4EjE|v4#1w`P*LjAhK)4J zhgCFW&^X1PB|Lzb%aX;YoWg{al!@mtRpSSy?OBgBB9aCW;OEV#(nUDwGD`?2$Xu$d zL{;v5k(O_|4goaEJW8o&%k?<0ll3QErD9c5gor|zb%~A2La}gqr9(-q!zV%s5816S z5i07ZbP?iaEL>?^1#Y*F%P6pd^E-@fH0An0;5C6>N?xZg9D*c@>POgyc1vLLi+Cv- zH}5W*ZSprDCm#ZcsmO%3A6&X&HcM89@Dc_uMRAEk)~N@j5(~*S%s08#rMk7hk8Xtr z0xFGqg&e}8uFy;;0A_}oq1J~7QdY;x?pxpc0ra^k2l3qY)HB)sJI(eKrUA-W^iCJd(6MAJctDa5phzBGhW(oL)4t<{GaI+1efGiEV1!@v{Q+sO7 z&P*@mCaxR14aN;hx9G_Z(^sg|e?MM>i$7k$KV>@9?LrzRxq%bKujAp&jU%Sr#)DD2 zPWl>NZ=NP6>Dg_Uo=xWnc$HJg#q4v$md8(V7T){5!k4d^$D$GcureFdfOIO_h=qUI zAt&%X#5>JAZ;R<3KM$S_0B8Lo9b?>0eQmCDJi9ZqW2ZUQE!vscA0b3)3qrUuzgIni zNDfwvi;6f8VKXk$6zBm?5EobO)cv2pwjbI1@Ni_c;0i!R0o4A$F*(k3UmSNkTwWA8 WUcwUcgCSAbT0;C?Q=QNLdjA81ed@RX literal 0 HcmV?d00001 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/_modules/examples/datastore_config_server.html b/_modules/examples/datastore_config_server.html new file mode 100644 index 0000000..d70e2a3 --- /dev/null +++ b/_modules/examples/datastore_config_server.html @@ -0,0 +1,154 @@ + + + + + + + examples.datastore_config_server — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + +
    +
    +
    +
    + +

    Source code for examples.datastore_config_server

    +#!/usr/local/bin/python
    +"""Simple XML-RPC Server to run on the datastore server.
    +
    +This daemon should be run on HiSPARC's datastore server.  It will
    +handle the cluster layouts and station passwords.  When an update is
    +necessary, it will reload the HTTP daemon.
    +
    +The basis for this code was ripped from the python SimpleXMLRPCServer
    +library documentation and extended.
    +
    +"""
    +
    +import hashlib
    +import os
    +import urllib.error
    +import urllib.parse
    +import urllib.request
    +
    +from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
    +
    +HASH = '/tmp/hash_datastore'
    +DATASTORE_CFG = '/databases/frome/station_list.csv'
    +CFG_URL = 'https://data.hisparc.nl/config/datastore'
    +DATASTORE_XMLRPC_SERVER = ('192.168.99.13', 8001)
    +RELOAD_PATH = '/tmp/uwsgi-reload.me'
    +
    +
    +
    +[docs] +def reload_datastore(): + """Load datastore config and reload datastore, if necessary""" + + datastore_cfg = urllib.request.urlopen(CFG_URL).read() + new_hash = hashlib.sha1(datastore_cfg).hexdigest() + + try: + with open(HASH) as file: + old_hash = file.readline() + except OSError: + old_hash = None + + if new_hash == old_hash: + return True + else: + with open(DATASTORE_CFG, 'w') as file: + file.write(datastore_cfg) + + # reload uWSGI + with open(RELOAD_PATH, 'a'): + os.utime(RELOAD_PATH, None) + + with open(HASH, 'w') as file: + file.write(new_hash) + + return True
    + + + +if __name__ == '__main__': + # Restrict to a particular path. + class RequestHandler(SimpleXMLRPCRequestHandler): + rpc_paths = ('/RPC2',) + + # Create server + server = SimpleXMLRPCServer(DATASTORE_XMLRPC_SERVER, requestHandler=RequestHandler) + server.register_introspection_functions() + + server.register_function(reload_datastore) + + # Run the server's main loop + server.serve_forever() +
    + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000..0d86565 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,86 @@ + + + + + + + Overview: module code — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + +
    +
    +
    + +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/_modules/writer/storage.html b/_modules/writer/storage.html new file mode 100644 index 0000000..e726ee3 --- /dev/null +++ b/_modules/writer/storage.html @@ -0,0 +1,471 @@ + + + + + + + writer.storage — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + +
    +
    +
    +
    + +

    Source code for writer.storage

    +"""Storage docstrings"""
    +
    +import tables
    +
    +
    +class HisparcEvent(tables.IsDescription):
    +    # DISCUSS: use of signed (dflt -1) vs unsigned (labview code)
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    nanoseconds = tables.UInt32Col(pos=2)
    +    ext_timestamp = tables.UInt64Col(pos=3)
    +    data_reduction = tables.BoolCol(pos=4)
    +    trigger_pattern = tables.UInt32Col(pos=5)
    +    baseline = tables.Int16Col(shape=4, dflt=-1, pos=6)
    +    std_dev = tables.Int16Col(shape=4, dflt=-1, pos=7)
    +    n_peaks = tables.Int16Col(shape=4, dflt=-1, pos=8)
    +    pulseheights = tables.Int16Col(shape=4, dflt=-1, pos=9)
    +    integrals = tables.Int32Col(shape=4, dflt=-1, pos=10)
    +    traces = tables.Int32Col(shape=4, dflt=-1, pos=11)
    +    event_rate = tables.Float32Col(pos=12)
    +
    +
    +class HisparcError(tables.IsDescription):
    +    """HiSPARC Error messages tables"""
    +
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    messages = tables.Int32Col(dflt=-1, pos=2)
    +
    +
    +class HisparcConfiguration(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    gps_latitude = tables.Float64Col(pos=2)
    +    gps_longitude = tables.Float64Col(pos=3)
    +    gps_altitude = tables.Float64Col(pos=4)
    +    mas_version = tables.Int32Col(dflt=-1, pos=5)
    +    slv_version = tables.Int32Col(dflt=-1, pos=6)
    +    trig_low_signals = tables.UInt32Col(pos=7)
    +    trig_high_signals = tables.UInt32Col(pos=8)
    +    trig_external = tables.UInt32Col(pos=9)
    +    trig_and_or = tables.BoolCol(pos=10)
    +    precoinctime = tables.Float64Col(pos=11)
    +    coinctime = tables.Float64Col(pos=12)
    +    postcoinctime = tables.Float64Col(pos=13)
    +    detnum = tables.UInt16Col(pos=14)
    +    password = tables.Int32Col(dflt=-1, pos=15)
    +    spare_bytes = tables.UInt8Col(pos=16)
    +    use_filter = tables.BoolCol(pos=17)
    +    use_filter_threshold = tables.BoolCol(pos=18)
    +    reduce_data = tables.BoolCol(pos=19)
    +    buffer = tables.Int32Col(dflt=-1, pos=20)
    +    startmode = tables.BoolCol(pos=21)
    +    delay_screen = tables.Float64Col(pos=22)
    +    delay_check = tables.Float64Col(pos=23)
    +    delay_error = tables.Float64Col(pos=24)
    +    mas_ch1_thres_low = tables.Float64Col(pos=25)
    +    mas_ch1_thres_high = tables.Float64Col(pos=26)
    +    mas_ch2_thres_low = tables.Float64Col(pos=27)
    +    mas_ch2_thres_high = tables.Float64Col(pos=28)
    +    mas_ch1_inttime = tables.Float64Col(pos=29)
    +    mas_ch2_inttime = tables.Float64Col(pos=30)
    +    mas_ch1_voltage = tables.Float64Col(pos=31)
    +    mas_ch2_voltage = tables.Float64Col(pos=32)
    +    mas_ch1_current = tables.Float64Col(pos=33)
    +    mas_ch2_current = tables.Float64Col(pos=34)
    +    mas_comp_thres_low = tables.Float64Col(pos=35)
    +    mas_comp_thres_high = tables.Float64Col(pos=36)
    +    mas_max_voltage = tables.Float64Col(pos=37)
    +    mas_reset = tables.BoolCol(pos=38)
    +    mas_ch1_gain_pos = tables.UInt8Col(pos=39)
    +    mas_ch1_gain_neg = tables.UInt8Col(pos=40)
    +    mas_ch2_gain_pos = tables.UInt8Col(pos=41)
    +    mas_ch2_gain_neg = tables.UInt8Col(pos=42)
    +    mas_ch1_offset_pos = tables.UInt8Col(pos=43)
    +    mas_ch1_offset_neg = tables.UInt8Col(pos=44)
    +    mas_ch2_offset_pos = tables.UInt8Col(pos=45)
    +    mas_ch2_offset_neg = tables.UInt8Col(pos=46)
    +    mas_common_offset = tables.UInt8Col(pos=47)
    +    mas_internal_voltage = tables.UInt8Col(pos=48)
    +    mas_ch1_adc_gain = tables.Float64Col(pos=49)
    +    mas_ch1_adc_offset = tables.Float64Col(pos=50)
    +    mas_ch2_adc_gain = tables.Float64Col(pos=51)
    +    mas_ch2_adc_offset = tables.Float64Col(pos=52)
    +    mas_ch1_comp_gain = tables.Float64Col(pos=53)
    +    mas_ch1_comp_offset = tables.Float64Col(pos=54)
    +    mas_ch2_comp_gain = tables.Float64Col(pos=55)
    +    mas_ch2_comp_offset = tables.Float64Col(pos=56)
    +    slv_ch1_thres_low = tables.Float64Col(pos=57)
    +    slv_ch1_thres_high = tables.Float64Col(pos=58)
    +    slv_ch2_thres_low = tables.Float64Col(pos=59)
    +    slv_ch2_thres_high = tables.Float64Col(pos=60)
    +    slv_ch1_inttime = tables.Float64Col(pos=61)
    +    slv_ch2_inttime = tables.Float64Col(pos=62)
    +    slv_ch1_voltage = tables.Float64Col(pos=63)
    +    slv_ch2_voltage = tables.Float64Col(pos=64)
    +    slv_ch1_current = tables.Float64Col(pos=65)
    +    slv_ch2_current = tables.Float64Col(pos=66)
    +    slv_comp_thres_low = tables.Float64Col(pos=67)
    +    slv_comp_thres_high = tables.Float64Col(pos=68)
    +    slv_max_voltage = tables.Float64Col(pos=69)
    +    slv_reset = tables.BoolCol(pos=70)
    +    slv_ch1_gain_pos = tables.UInt8Col(pos=71)
    +    slv_ch1_gain_neg = tables.UInt8Col(pos=72)
    +    slv_ch2_gain_pos = tables.UInt8Col(pos=73)
    +    slv_ch2_gain_neg = tables.UInt8Col(pos=74)
    +    slv_ch1_offset_pos = tables.UInt8Col(pos=75)
    +    slv_ch1_offset_neg = tables.UInt8Col(pos=76)
    +    slv_ch2_offset_pos = tables.UInt8Col(pos=77)
    +    slv_ch2_offset_neg = tables.UInt8Col(pos=78)
    +    slv_common_offset = tables.UInt8Col(pos=79)
    +    slv_internal_voltage = tables.UInt8Col(pos=80)
    +    slv_ch1_adc_gain = tables.Float64Col(pos=81)
    +    slv_ch1_adc_offset = tables.Float64Col(pos=82)
    +    slv_ch2_adc_gain = tables.Float64Col(pos=83)
    +    slv_ch2_adc_offset = tables.Float64Col(pos=84)
    +    slv_ch1_comp_gain = tables.Float64Col(pos=85)
    +    slv_ch1_comp_offset = tables.Float64Col(pos=86)
    +    slv_ch2_comp_gain = tables.Float64Col(pos=87)
    +    slv_ch2_comp_offset = tables.Float64Col(pos=88)
    +
    +
    +class HisparcComparator(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    nanoseconds = tables.UInt32Col(pos=2)
    +    ext_timestamp = tables.UInt64Col(pos=3)
    +    device = tables.UInt8Col(pos=4)
    +    comparator = tables.UInt8Col(pos=5)
    +    count = tables.UInt16Col(pos=6)
    +
    +
    +class HisparcSingle(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    mas_ch1_low = tables.Int32Col(dflt=-1, pos=2)
    +    mas_ch1_high = tables.Int32Col(dflt=-1, pos=3)
    +    mas_ch2_low = tables.Int32Col(dflt=-1, pos=4)
    +    mas_ch2_high = tables.Int32Col(dflt=-1, pos=5)
    +    slv_ch1_low = tables.Int32Col(dflt=-1, pos=6)
    +    slv_ch1_high = tables.Int32Col(dflt=-1, pos=7)
    +    slv_ch2_low = tables.Int32Col(dflt=-1, pos=8)
    +    slv_ch2_high = tables.Int32Col(dflt=-1, pos=9)
    +
    +
    +class HisparcSatellite(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    min_n = tables.UInt16Col(pos=2)
    +    mean_n = tables.Float32Col(pos=3)
    +    max_n = tables.UInt16Col(pos=4)
    +    min_signal = tables.UInt16Col(pos=5)
    +    mean_signal = tables.Float32Col(pos=6)
    +    max_signal = tables.UInt16Col(pos=7)
    +
    +
    +class WeatherEvent(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    temp_inside = tables.Float32Col(pos=2)
    +    temp_outside = tables.Float32Col(pos=3)
    +    humidity_inside = tables.Int16Col(pos=4)
    +    humidity_outside = tables.Int16Col(pos=5)
    +    barometer = tables.Float32Col(pos=6)
    +    wind_dir = tables.Int16Col(pos=7)
    +    wind_speed = tables.Int16Col(pos=8)
    +    solar_rad = tables.Int16Col(pos=9)
    +    uv = tables.Int16Col(pos=10)
    +    evapotranspiration = tables.Float32Col(pos=11)
    +    rain_rate = tables.Float32Col(pos=12)
    +    heat_index = tables.Int16Col(pos=13)
    +    dew_point = tables.Float32Col(pos=14)
    +    wind_chill = tables.Float32Col(pos=15)
    +
    +
    +class WeatherError(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    messages = tables.Int32Col(dflt=-1, pos=2)
    +
    +
    +class WeatherConfig(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    com_port = tables.UInt8Col(pos=2)
    +    baud_rate = tables.Int16Col(pos=3)
    +    station_id = tables.UInt32Col(pos=4)
    +    database_name = tables.Int32Col(dflt=-1, pos=5)
    +    help_url = tables.Int32Col(dflt=-1, pos=6)
    +    daq_mode = tables.BoolCol(pos=7)
    +    latitude = tables.Float64Col(pos=8)
    +    longitude = tables.Float64Col(pos=9)
    +    altitude = tables.Float64Col(pos=10)
    +    temperature_inside = tables.BoolCol(pos=11)
    +    temperature_outside = tables.BoolCol(pos=12)
    +    humidity_inside = tables.BoolCol(pos=13)
    +    humidity_outside = tables.BoolCol(pos=14)
    +    barometer = tables.BoolCol(pos=15)
    +    wind_direction = tables.BoolCol(pos=16)
    +    wind_speed = tables.BoolCol(pos=17)
    +    solar_radiation = tables.BoolCol(pos=18)
    +    uv_index = tables.BoolCol(pos=19)
    +    evapotranspiration = tables.BoolCol(pos=20)
    +    rain_rate = tables.BoolCol(pos=21)
    +    heat_index = tables.BoolCol(pos=22)
    +    dew_point = tables.BoolCol(pos=23)
    +    wind_chill = tables.BoolCol(pos=24)
    +    offset_inside_temperature = tables.Float32Col(pos=25)
    +    offset_outside_temperature = tables.Float32Col(pos=26)
    +    offset_inside_humidity = tables.Int16Col(pos=27)
    +    offset_outside_humidity = tables.Int16Col(pos=28)
    +    offset_wind_direction = tables.Int16Col(pos=29)
    +    offset_station_altitude = tables.Float32Col(pos=30)
    +    offset_bar_sea_level = tables.Float32Col(pos=31)
    +
    +
    +class LightningEvent(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    corr_distance = tables.Int16Col(pos=2)
    +    uncorr_distance = tables.Int16Col(pos=3)
    +    uncorr_angle = tables.Float32Col(pos=4)
    +    corr_angle = tables.Float32Col(pos=5)
    +
    +
    +class LightningError(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    messages = tables.Int32Col(dflt=-1, pos=2)
    +
    +
    +class LightningConfig(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    com_port = tables.UInt8Col(pos=2)
    +    baud_rate = tables.Int16Col(pos=3)
    +    station_id = tables.UInt32Col(pos=4)
    +    database_name = tables.Int32Col(dflt=-1, pos=5)
    +    help_url = tables.Int32Col(dflt=-1, pos=6)
    +    daq_mode = tables.BoolCol(pos=7)
    +    latitude = tables.Float64Col(pos=8)
    +    longitude = tables.Float64Col(pos=9)
    +    altitude = tables.Float64Col(pos=10)
    +    squelch_seting = tables.Int32Col(pos=11)
    +    close_alarm_distance = tables.Int32Col(pos=12)
    +    severe_alarm_distance = tables.Int32Col(pos=13)
    +    noise_beep = tables.BoolCol(pos=14)
    +    minimum_gps_speed = tables.Int32Col(pos=15)
    +    angle_correction = tables.Float32Col(pos=16)
    +
    +
    +class LightningStatus(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +    close_rate = tables.Int16Col(pos=2)
    +    total_rate = tables.Int16Col(pos=3)
    +    close_alarm = tables.BoolCol(pos=4)
    +    sever_alarm = tables.BoolCol(pos=5)
    +    current_heading = tables.Float32Col(pos=6)
    +
    +
    +class LightningNoise(tables.IsDescription):
    +    event_id = tables.UInt32Col(pos=0)
    +    timestamp = tables.Time32Col(pos=1)
    +
    +
    +
    +[docs] +def open_or_create_file(data_dir, date): + """Open an existing file or create a new one + + This function opens an existing PyTables file according to the event + date. If the file does not yet exist, a new one is created. + + :param data_dir: the directory containing all data files + :param date: the event date + + """ + directory = data_dir / f'{date.year}/{date.month}' + file = directory / f'{date.year}_{date.month}_{date.day}.h5' + + # Ensure dir and parent directories exist with mode rwxr-xr-x + directory.mkdir(mode=0o755, parents=True, exist_ok=True) + + return tables.open_file(file, 'a')
    + + + +
    +[docs] +def get_or_create_station_group(file, cluster, station_id): + """Get an existing station group or create a new one + + :param file: the PyTables data file + :param cluster: the name of the cluster + :param station_id: the station number + + """ + cluster = get_or_create_cluster_group(file, cluster) + node_name = f'station_{station_id}' + try: + station = file.get_node(cluster, node_name) + except tables.NoSuchNodeError: + station = file.create_group(cluster, node_name, f'HiSPARC station {station_id} data') + file.flush() + + return station
    + + + +
    +[docs] +def get_or_create_cluster_group(file, cluster): + """Get an existing cluster group or create a new one + + :param file: the PyTables data file + :param cluster: the name of the cluster + + """ + try: + hisparc = file.get_node('/', 'hisparc') + except tables.NoSuchNodeError: + hisparc = file.create_group('/', 'hisparc', 'HiSPARC data') + file.flush() + + node_name = 'cluster_' + cluster.lower() + try: + cluster = file.get_node(hisparc, node_name) + except tables.NoSuchNodeError: + cluster = file.create_group(hisparc, node_name, f'HiSPARC cluster {cluster} data') + file.flush() + + return cluster
    + + + +
    +[docs] +def get_or_create_node(file, cluster, node): + """Get an existing node or create a new one + + :param file: the PyTables data file + :param cluster: the parent (cluster) node + :param node: the node (e.g. events, blobs) + + """ + try: + node = file.get_node(cluster, node) + except tables.NoSuchNodeError: + if node == 'events': + node = file.create_table(cluster, 'events', HisparcEvent, 'HiSPARC event data') + elif node == 'errors': + node = file.create_table(cluster, 'errors', HisparcError, 'HiSPARC error messages') + elif node == 'config': + node = file.create_table(cluster, 'config', HisparcConfiguration, 'HiSPARC configuration messages') + elif node == 'comparator': + node = file.create_table(cluster, 'comparator', HisparcComparator, 'HiSPARC comparator messages') + elif node == 'singles': + node = file.create_table(cluster, 'singles', HisparcSingle, 'HiSPARC single messages') + elif node == 'satellites': + node = file.create_table(cluster, 'satellites', HisparcSatellite, 'HiSPARC satellite messages') + elif node == 'blobs': + node = file.create_vlarray(cluster, 'blobs', tables.VLStringAtom(), 'HiSPARC binary data') + elif node == 'weather': + node = file.create_table(cluster, 'weather', WeatherEvent, 'HiSPARC weather data') + elif node == 'weather_errors': + node = file.create_table(cluster, 'weather_errors', WeatherError, 'HiSPARC weather error messages') + elif node == 'weather_config': + node = file.create_table(cluster, 'weather_config', WeatherConfig, 'HiSPARC weather configuration messages') + elif node == 'lightning': + node = file.create_table(cluster, 'lightning', LightningEvent, 'HiSPARC lightning data') + elif node == 'lightning_errors': + node = file.create_table(cluster, 'lightning_errors', LightningError, 'HiSPARC lightning error messages') + elif node == 'lightning_config': + node = file.create_table( + cluster, + 'lightning_config', + LightningConfig, + 'HiSPARC lightning configuration messages', + ) + elif node == 'lightning_status': + node = file.create_table(cluster, 'lightning_status', LightningStatus, 'HiSPARC lightning status messages') + elif node == 'lightning_noise': + node = file.create_table(cluster, 'lightning_noise', LightningNoise, 'HiSPARC lightning noise messages') + file.flush() + + return node
    + +
    + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/_modules/writer/store_events.html b/_modules/writer/store_events.html new file mode 100644 index 0000000..93b5895 --- /dev/null +++ b/_modules/writer/store_events.html @@ -0,0 +1,230 @@ + + + + + + + writer.store_events — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + +
    +
    +
    +
    + +

    Source code for writer.store_events

    +import base64
    +import calendar
    +import logging
    +
    +from writer import storage
    +from writer.upload_codes import eventtype_upload_codes
    +
    +logger = logging.getLogger('writer.store_events')
    +
    +MINIMUM_YEAR = 2020
    +
    +
    +
    +[docs] +def store_event(datafile, cluster, station_id, event): + """Stores an event in the h5 filesystem + + :param datafile: the h5 data file + :param cluster: the name of the cluster to which the station belongs + :param station_id: the id of the station this event belongs to + :param event: the event to store + + """ + eventheader = event['header'] + eventdatalist = event['datalist'] + eventtype = eventheader['eventtype_uploadcode'] + + try: + upload_codes = eventtype_upload_codes[eventtype] + except KeyError: + logger.error(f'Unknown event type: {eventtype}, discarding event (station: {station_id})') + return + + parentnode = storage.get_or_create_station_group(datafile, cluster, station_id) + table = storage.get_or_create_node(datafile, parentnode, upload_codes['_tablename']) + blobs = storage.get_or_create_node(datafile, parentnode, 'blobs') + + row = table.row + row['event_id'] = table.nrows + 1 + # make a unix-like timestamp + timestamp = calendar.timegm(eventheader['datetime'].utctimetuple()) + nanoseconds = eventheader['nanoseconds'] + # make an extended timestamp, which is the number of nanoseconds since + # epoch + ext_timestamp = timestamp * 1_000_000_000 + nanoseconds + row['timestamp'] = timestamp + + if upload_codes['_has_ext_time']: + # This is e.g. a HiSPARC coincidence or comparator message, + # extended timing information is available + row['nanoseconds'] = nanoseconds + row['ext_timestamp'] = ext_timestamp + + # get default values for the data + data = {} + for key, value in upload_codes.items(): + if key[0] != '_': + # private meta information starts with a _ (e.g. _tablename) + data[key] = row[value] + + # process event data + for item in eventdatalist: + # uploadcode: EVENTRATE, PH1, IN3, etc. + uploadcode = item['data_uploadcode'] + # value: actual data value + value = item['data'] + + if data_is_blob(uploadcode, upload_codes['_blobs']): + # data should be stored inside the blob array, ... + if uploadcode[:-1] == 'TR': + # traces are base64 encoded + value = base64.decodebytes(value.encode('iso-8859-1')) + else: + # blobs are bytestrings + value = value.encode('iso-8859-1') + blobs.append(value) + # ... with a pointer stored in the event table + value = len(blobs) - 1 + + if uploadcode[-1].isdigit(): + # uploadcode: PH1, IN3, etc. + key, index = uploadcode[:-1], int(uploadcode[-1]) - 1 + if key in data: + data[key][index] = value + else: + logger.warning(f'Datatype not known on server side: {key} ({eventtype})') + elif uploadcode in data: + # uploadcode: EVENTRATE, RED, etc. + data[uploadcode] = value + else: + logger.warning(f'Datatype not known on server side: {uploadcode} ({eventtype})') + + # write data values to row + for key, value in upload_codes.items(): + if key[0] != '_': + # private meta information starts with a _ (e.g. _tablename) + row[value] = data[key] + + row.append() + table.flush() + blobs.flush()
    + + + +
    +[docs] +def data_is_blob(uploadcode, blob_types): + """Determine if data is a variable length binary value (blob)""" + + if uploadcode[-1].isdigit(): + if uploadcode[:-1] in blob_types: + return True + elif uploadcode in blob_types: + return True + return False
    + + + +
    +[docs] +def store_event_list(data_dir, station_id, cluster, event_list): + """Store a list of events""" + + prev_date = None + datafile = None + for event in event_list: + try: + timestamp = event['header']['datetime'] + if timestamp: + date = timestamp.date() + if date.year < MINIMUM_YEAR: + logger.error(f'Old event ({date}), discarding event (station: {station_id})') + continue + if date != prev_date: + if datafile: + datafile.close() + datafile = storage.open_or_create_file(data_dir, date) + prev_date = date + store_event(datafile, cluster, station_id, event) + else: + logger.error(f'Strange event (no timestamp!), discarding event (station: {station_id})') + except Exception: + logger.exception(f'Cannot process event, discarding event (station: {station_id})') + + if datafile: + datafile.close()
    + +
    + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/_modules/writer/writer_app.html b/_modules/writer/writer_app.html new file mode 100644 index 0000000..480e58e --- /dev/null +++ b/_modules/writer/writer_app.html @@ -0,0 +1,196 @@ + + + + + + + writer.writer_app — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + +
    +
    +
    +
    + +

    Source code for writer.writer_app

    +"""datastore writer application
    +
    +This module empties the station data `incoming` queue and writes the
    +data into HDF5 files using PyTables.
    +
    +"""
    +
    +import configparser
    +import logging
    +import logging.handlers
    +import pickle
    +import time
    +
    +from pathlib import Path
    +
    +from writer.store_events import store_event_list
    +
    +LEVELS = {
    +    'debug': logging.DEBUG,
    +    'info': logging.INFO,
    +    'warning': logging.WARNING,
    +    'error': logging.ERROR,
    +    'critical': logging.CRITICAL,
    +}
    +
    +logger = logging.getLogger('writer')
    +formatter = logging.Formatter('%(asctime)s %(name)s[%(process)d].%(funcName)s.%(levelname)s: %(message)s')
    +
    +
    +
    +[docs] +def writer(configfile): + """hisparc datastore writer application + + This script polls ``/datatore/frome/incoming`` for incoming data written + by the WSGI app. It then store the data into the raw datastore. + + Configuration is read from the datastore configuation file (usually + `config.ini`): + + .. include:: ../examples/config.ini + :literal: + + """ + # set up config + global config + config = configparser.ConfigParser() + config.read(configfile) + + # set up logger + file = config.get('General', 'log') + '-writer' + handler = logging.handlers.TimedRotatingFileHandler(file, when='midnight', backupCount=14) + handler.setFormatter(formatter) + logger.addHandler(handler) + level = LEVELS.get(config.get('General', 'loglevel'), logging.NOTSET) + logger.setLevel(level=level) + + data_dir = Path(config.get('General', 'data_dir')) + queue = data_dir / 'incoming' + partial_queue = data_dir / 'partial' + + sleep_duration = config.getint('Writer', 'sleep') + + # writer process + try: + while True: + entries = queue.iterdir() + + if not entries: + time.sleep(sleep_duration) + + for entry in entries: + partial_path = partial_queue / entry.name + entry.rename(partial_path) + + process_data(partial_path, data_dir) + partial_path.unlink() + + except Exception: + logger.exception('Exception occured, quitting.')
    + + + +
    +[docs] +def process_data(file, data_dir): + """Read data from a pickled object and store store in raw datastore""" + with file.open('rb') as handle: + try: + data = pickle.load(handle) + except UnicodeDecodeError: + logger.debug('Data seems to be pickled using python 2. Decoding.') + data = decode_object(pickle.load(handle, encoding='bytes')) + + logger.debug(f"Processing data for station {data['station_id']}") + store_event_list(data_dir, data['station_id'], data['cluster'], data['event_list'])
    + + + +
    +[docs] +def decode_object(o): + """recursively decode all bytestrings in object""" + + if isinstance(o, bytes): + return o.decode() + elif isinstance(o, dict): + return {decode_object(k): decode_object(v) for k, v in o.items()} + elif isinstance(o, list): + return [decode_object(obj) for obj in o] + else: + return o
    + +
    + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/_modules/wsgi/wsgi_app.html b/_modules/wsgi/wsgi_app.html new file mode 100644 index 0000000..3306c14 --- /dev/null +++ b/_modules/wsgi/wsgi_app.html @@ -0,0 +1,299 @@ + + + + + + + wsgi.wsgi_app — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + +
    +
    +
    +
    + +

    Source code for wsgi.wsgi_app

    +import configparser
    +import csv
    +import hashlib
    +import logging
    +import logging.handlers
    +import os
    +import pickle
    +import shutil
    +import tempfile
    +import urllib.parse
    +
    +from pathlib import Path
    +
    +from . import rcodes
    +
    +LEVELS = {
    +    'debug': logging.DEBUG,
    +    'info': logging.INFO,
    +    'warning': logging.WARNING,
    +    'error': logging.ERROR,
    +    'critical': logging.CRITICAL,
    +}
    +
    +logger = logging.getLogger('wsgi_app')
    +formatter = logging.Formatter('%(asctime)s %(name)s[%(process)d].%(funcName)s.%(levelname)s: %(message)s')
    +
    +MINIMUM_YEAR = 2020
    +
    +
    +
    +[docs] +def application(environ, start_response, configfile): + """The hisparc upload application + + This handler is called by uWSGI whenever someone requests our URL. + + First, we generate a dictionary of POSTed variables and try to read out the + station_id, password, checksum and data. When we do a readline(), we + already read out the entire datastream. I don't know if we can first check + on station_id/password combinations before reading out the datastream + without setting up a bidirectional communication channel. + + When the checksum matches, we unpickle the event_list and pass everything + on to store_event_list. + + """ + do_init(configfile) + + # start http response + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + + # read data from the POST variables + post_input = environ['wsgi.input'].readline().decode() + post_data = urllib.parse.parse_qs(post_input) + + # process POST data + try: + data = post_data['data'][0] + checksum = post_data['checksum'][0] + station_id = int(post_data['station_id'][0]) + password = post_data['password'][0] + except (KeyError, EOFError): + logger.debug('POST (vars) error') + return [rcodes.RC_ISE_INV_POSTDATA] + + try: + cluster, station_password = station_list[station_id] + except KeyError: + logger.debug(f'Station {station_id} is unknown') + return [rcodes.RC_PE_INV_STATIONID] + + if station_password != password: + logger.debug(f'Station {station_id}: password mismatch: {password}') + return [rcodes.RC_PE_INV_AUTHCODE] + else: + our_checksum = hashlib.md5(data.encode('iso-8859-1')).hexdigest() + if our_checksum != checksum: + logger.debug(f'Station {station_id}: checksum mismatch') + return [rcodes.RC_PE_INV_INPUT] + else: + try: + try: + event_list = pickle.loads(data.encode('iso-8859-1')) + except UnicodeDecodeError: + # string was probably pickled on python 2. + # decode as bytes and decode all bytestrings to string. + logger.debug('UnicodeDecodeError on python 2 pickle. Decoding bytestrings.') + event_list = decode_object(pickle.loads(data.encode('iso-8859-1'), encoding='bytes')) + except (pickle.UnpicklingError, AttributeError): + logger.debug(f'Station {station_id}: pickling error') + return [rcodes.RC_PE_PICKLING_ERROR] + + store_data(station_id, cluster, event_list) + logger.debug(f'Station {station_id}: succesfully completed') + return [rcodes.RC_OK]
    + + + +
    +[docs] +def do_init(configfile): + """Load configuration and passwords and set up a logger handler + + This function will do one-time initialization. By using global + variables, we eliminate the need to reread configuration and passwords + on every request. + + Configuration is read from the datastore configuation file (usually + `config.ini`): + + .. include:: ../examples/config.ini + :literal: + + Station information is read from the `station_list` config variable. + (`station_list.csv` on frome) + + """ + # set up config + global config + try: + config + except NameError: + config = configparser.ConfigParser() + config.read(configfile) + + # set up logger + if not logger.handlers: + file = config.get('General', 'log') + f'-wsgi.{os.getpid()}' + handler = logging.handlers.TimedRotatingFileHandler(file, when='midnight', backupCount=14) + handler.setFormatter(formatter) + logger.addHandler(handler) + level = LEVELS.get(config.get('General', 'loglevel'), logging.NOTSET) + logger.setLevel(level=level) + + # read station list + global station_list + try: + station_list + except NameError: + station_list = {} + station_list_path = Path(config.get('General', 'station_list')) + with station_list_path.open() as file: + reader = csv.reader(file) + for station in reader: + if station: + num, cluster, password = station + num = int(num) + station_list[num] = (cluster, password)
    + + + +
    +[docs] +def store_data(station_id, cluster, event_list): + """Store verified event data to temporary storage""" + + logger.debug(f'Storing data for station {station_id}') + + data_dir = Path(config.get('General', 'data_dir')) + + directory = data_dir / 'incoming' + tmp_dir = data_dir / 'tmp' + + if is_data_suspicious(event_list): + logger.debug('Event list marked as suspicious.') + directory = data_dir / 'suspicious' + + file = tempfile.NamedTemporaryFile(dir=tmp_dir, delete=False) + logger.debug(f'Filename: {file.name}') + data = {'station_id': station_id, 'cluster': cluster, 'event_list': event_list} + pickle.dump(data, file) + file.close() + + shutil.move(file.name, directory)
    + + + +
    +[docs] +def is_data_suspicious(event_list): + """Check data for suspiciousness + + Suspiciousness, a previously unknown quantum number that may signify + the actual birth of the universe and the reweaving of past fates into + current events has come to hunt us and our beloved data. + + Note: Apr 7, 2019 0:00 is the default time after a cold start without GPS signal. + The DAQ will happily send events even when no GPS signal has been + acquired (yet). Events with timestamp Apr 7, 2019 are most probably caused + by no or bad GPS signal. Such events must be eigenstates of suspiciousness. + + """ + for event in event_list: + if event['header']['datetime'].year < MINIMUM_YEAR: + logger.debug('Date < 2020: Timestamp has high suspiciousness.') + return True + return False
    + + + +
    +[docs] +def decode_object(o): + """recursively decode all bytestrings in object""" + + if isinstance(o, bytes): + return o.decode() + elif isinstance(o, dict): + return {decode_object(key): decode_object(value) for key, value in o.items()} + elif isinstance(o, list): + return [decode_object(obj) for obj in o] + else: + return o
    + +
    + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 0000000..29a4fa7 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,25 @@ +.. HiSPARC-datastore documentation master file, created by + sphinx-quickstart on Wed May 10 10:11:52 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to HiSPARC-datastore's documentation! +============================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + introduction + wsgi + writer + xml-rpc-server + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/_sources/introduction.rst.txt b/_sources/introduction.rst.txt new file mode 100644 index 0000000..6b441ca --- /dev/null +++ b/_sources/introduction.rst.txt @@ -0,0 +1,11 @@ +Introduction +============ + +Todo: + + - document datastore setup on frome + - document `station-list.csv` <-- pique + - document raw datastore tables + - document upload codes?? + - document logfiles + diff --git a/_sources/writer.rst.txt b/_sources/writer.rst.txt new file mode 100644 index 0000000..772ae2c --- /dev/null +++ b/_sources/writer.rst.txt @@ -0,0 +1,37 @@ +datastore writer package +======================== + +.. automodule:: writer + :members: + :undoc-members: + + +Writer application +------------------ + +.. automodule:: writer.writer_app + :members: + :undoc-members: + +Storage classes - raw datastore tables +-------------------------------------- + +.. automodule:: writer.storage + :members: + :undoc-members: + :imported-members: + +Store events module +------------------- + +.. automodule:: writer.store_events + :members: + :undoc-members: + +Upload codes +------------ + + +.. automodule:: writer.upload_codes + :members: + :undoc-members: diff --git a/_sources/wsgi.rst.txt b/_sources/wsgi.rst.txt new file mode 100644 index 0000000..b59801a --- /dev/null +++ b/_sources/wsgi.rst.txt @@ -0,0 +1,22 @@ +datastore WSGI app package +========================== + +.. automodule:: wsgi + :members: + :undoc-members: + + +wsgi\.rcodes module +------------------- + +.. automodule:: wsgi.rcodes + :members: + :undoc-members: + + +wsgi\.wsgi\_app module +---------------------- + +.. automodule:: wsgi.wsgi_app + :members: + :undoc-members: diff --git a/_sources/xml-rpc-server.rst.txt b/_sources/xml-rpc-server.rst.txt new file mode 100644 index 0000000..1373ca7 --- /dev/null +++ b/_sources/xml-rpc-server.rst.txt @@ -0,0 +1,6 @@ +XML-RPC-Server +============== + +.. automodule:: examples.datastore_config_server + :members: + :undoc-members: diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000..f316efc --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..4d67807 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..89435bb --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '1.0.0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/favicon.ico b/_static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2632b900af50d910fc8f276420be1a235eb8c5c2 GIT binary patch literal 4286 zcma)8OQ=p^6g}P|T?rWoWuhdBB+)f7VuUg?QA#Py#NZ~6%uEbSq`YEA29%V?0EHt` z2J*-sk;H^ZK5HF&9qa$j_shAhyZ5)(+IyXS`u}rpRgJ>Wym=LWSLdfz)y%4@762$! zYXH^s8_Yep%8@AK0+*P{Lcp#OA0^Jj(c*odD(L)}x&K;E~r z>Wbu*E2>8v;&&M}YY<*W-L9 z>U=iit*`M$t&QS5lGnUki}NHvyD>J;mb&OUX8g3$9BIWddM=*P^U%ykjXUceR$Ko8 ztIy77auy-I^U9@BbZuz(y`le4peG*rEOoDksmI3 z=QU{C=Tge$sXl4qJPv2R*YvFQ&OIG`^KR1&_IiIlGv~ysUuV}L=ie;(^O;`^-5B*z zPvrfo4~o{$Jud_6tu`8GzR!OE^1l@xt+(lUZ#Z^8)6rApgIgLNcjK&G^tYV7kMr4n zrnmbe*9?6Y8(#IScM0m;@n%-n-mg?=zmU~hfAZv9hrExMCUhNt@Y?|Te{>(^9ytqW z_v6`l=Hl~ua3z0!*Xh4lJ;>X=R8uU?r{2)Fzxo()_4(thcm0##eNK~me`t#HbAO$^ z-~9FY=KqS^eT8&+AJ5|4ANA%w21WIFKXUDV1^FU?-p^w0804LuS@T&q|I=5z_gF6f zi_l#K=)XZcBd+!yJU34MtuE_tc+>m+%l{>2Q}@|*@?-Rwo<|=_Z2$W?Wgck#y)X3g z=ib!ql*anoy;PGXX0-F2SB_ZZ3;w^dxkX>j&2-KDLcMQ)@rniieNj_iC%Uh4%feD*}=|8~EhJU9RLdC)W0u~w>w?g%(~pHQFA^*+^0kveI`doAZoqupn@ zM!kcg^|!v#l2bk?hGrjl`kzye=K7g4y#Bwr`V>2TZ}$wP-ma0D_QjZ%0zPkIs9*e&7HwA*Gi?bzRS`>xaX@NL}Age{e6|7$R=tOxy|7h&5U< IIh_pr22!tZkN^Mx literal 0 HcmV?d00001 diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a858a410e4faa62ce324d814e4b816fff83a6fb3 GIT binary patch literal 286 zcmV+(0pb3MP)s`hMrGg#P~ix$^RISR_I47Y|r1 z_CyJOe}D1){SET-^Amu_i71Lt6eYfZjRyw@I6OQAIXXHDfiX^GbOlHe=Ae4>0m)d(f|Me07*qoM6N<$f}vM^LjV8( literal 0 HcmV?d00001 diff --git a/_static/header.png b/_static/header.png new file mode 100644 index 0000000000000000000000000000000000000000..383625f38ea5244e6b401fe8d3de52432ba78d40 GIT binary patch literal 3210 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 wHt@W;Ps#ng(R*rXRPZYB0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..d96755fdaf8bb2214971e0db9c1fd3077d7c419d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu=nj kDsEF_5m^0CR;1wuP-*O&G^0G}KYk!hp00i_>zopr08q^qX#fBK literal 0 HcmV?d00001 diff --git a/_static/nature.css b/_static/nature.css new file mode 100644 index 0000000..ba033b0 --- /dev/null +++ b/_static/nature.css @@ -0,0 +1,252 @@ +/* + * nature.css_t + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- nature theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Arial, sans-serif; + font-size: 100%; + background-color: #fff; + color: #555; + margin: 0; + padding: 0; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.document { + background-color: #eee; +} + +div.body { + background-color: #ffffff; + color: #3E4349; + padding: 0 30px 30px 30px; + font-size: 0.9em; +} + +div.footer { + color: #555; + width: 100%; + padding: 13px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #444; + text-decoration: underline; +} + +div.related { + background-color: #6BA81E; + line-height: 32px; + color: #fff; + text-shadow: 0px 1px 0 #444; + font-size: 0.9em; +} + +div.related a { + color: #E2F3CC; +} + +div.sphinxsidebar { + font-size: 0.75em; + line-height: 1.5em; +} + +div.sphinxsidebarwrapper{ + padding: 20px 0; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Arial, sans-serif; + color: #222; + font-size: 1.2em; + font-weight: normal; + margin: 0; + padding: 5px 10px; + background-color: #ddd; + text-shadow: 1px 1px 0 white +} + +div.sphinxsidebar h4{ + font-size: 1.1em; +} + +div.sphinxsidebar h3 a { + color: #444; +} + + +div.sphinxsidebar p { + color: #888; + padding: 5px 20px; +} + +div.sphinxsidebar p.topless { +} + +div.sphinxsidebar ul { + margin: 10px 20px; + padding: 0; + color: #000; +} + +div.sphinxsidebar a { + color: #444; +} + +div.sphinxsidebar input { + border: 1px solid #ccc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar .searchformwrapper { + margin-left: 20px; + margin-right: 20px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #005B81; + text-decoration: none; +} + +a:hover { + color: #E32E00; + text-decoration: underline; +} + +a:visited { + color: #551A8B; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Arial, sans-serif; + background-color: #BED4EB; + font-weight: normal; + color: #212224; + margin: 30px 0px 10px 0px; + padding: 5px 0 5px 10px; + text-shadow: 0px 1px 0 white +} + +div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 150%; background-color: #C8D5E3; } +div.body h3 { font-size: 120%; background-color: #D8DEE3; } +div.body h4 { font-size: 110%; background-color: #D8DEE3; } +div.body h5 { font-size: 100%; background-color: #D8DEE3; } +div.body h6 { font-size: 100%; background-color: #D8DEE3; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + line-height: 1.5em; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +nav.contents, +aside.topic, +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 10px; + line-height: 1.2em; + border: 1px solid #C6C9CB; + font-size: 1.1em; + margin: 1.5em 0 1.5em 0; + -webkit-box-shadow: 1px 1px 1px #d8d8d8; + -moz-box-shadow: 1px 1px 1px #d8d8d8; +} + +code { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ + font-size: 1.1em; + font-family: monospace; +} + +.viewcode-back { + font-family: Arial, sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +div.code-block-caption { + background-color: #ddd; + color: #222; + border: 1px solid #C6C9CB; +} \ No newline at end of file diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..7107cec93a979b9a5f64843235a16651d563ce2d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu>-2 m3q%Vub%g%s<8sJhVPMczOq}xhg9DJoz~JfX=d#Wzp$Pyb1r*Kz literal 0 HcmV?d00001 diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 0000000..0d49244 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #208050 } /* Literal.Number.Bin */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 0000000..b08d58c --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,620 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 0000000..8a96c69 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/genindex.html b/genindex.html new file mode 100644 index 0000000..1c4addc --- /dev/null +++ b/genindex.html @@ -0,0 +1,374 @@ + + + + + + + Index — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + +
    +
    +
    +
    + + +

    Index

    + +
    + A + | C + | D + | E + | G + | H + | I + | L + | M + | O + | P + | R + | S + | W + +
    +

    A

    + + +
    + +

    C

    + + +
    + +

    D

    + + + +
    + +

    E

    + + +
      +
    • + examples.datastore_config_server + +
    • +
    + +

    G

    + + + +
    + +

    H

    + + + +
    + +

    I

    + + +
    + +

    L

    + + + +
    + +

    M

    + + +
    + +

    O

    + + +
    + +

    P

    + + +
    + +

    R

    + + +
    + +

    S

    + + + +
    + +

    W

    + + + +
      +
    • + writer.upload_codes + +
    • +
    • + writer.writer_app + +
    • +
    • + wsgi + +
    • +
    • + wsgi.rcodes + +
    • +
    • + wsgi.wsgi_app + +
    • +
    + + + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..5e5428f --- /dev/null +++ b/index.html @@ -0,0 +1,141 @@ + + + + + + + + Welcome to HiSPARC-datastore’s documentation! — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + + +
    + + +
    +
    + + + + \ No newline at end of file diff --git a/introduction.html b/introduction.html new file mode 100644 index 0000000..eb20a9c --- /dev/null +++ b/introduction.html @@ -0,0 +1,125 @@ + + + + + + + + Introduction — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +

    Introduction

    +

    Todo:

    +
    +
      +
    • document datastore setup on frome

    • +
    • document station-list.csv <– pique

    • +
    • document raw datastore tables

    • +
    • document upload codes??

    • +
    • document logfiles

    • +
    +
    +
    + + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..810f3a2d1a49c9c5dc7ef01fb82cec89786253a2 GIT binary patch literal 881 zcmV-%1CIP7AX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkNX;V-^ zQbR3dVRT`0bZ>HH3L_v^WpZ_Ab7^j8AbMRI@p$h-+vc&`4a3|-AXXal38Y~ z(m=UtooM2ZER&{}Gcik0+G?ZuzE zhFhT;ay2RMbuHWWs$jSlt#7=%6{cmH->BOCFsJ3ExMaqV;)?v%!VUtj&)y!ZU;@nR zd-prn(0hnA5wkffU$sVeATU|gXJ{-|wP$jwYi2=8$0C)FTvlB)l|)MiXj1oHR6EVz^Th9fGk{9OqHXyriVmDiTp&Y-1Vu}SR>FuMbdi(v}obW!n3 zn5-%$b_r7~LT$44#&EReraE{cx00qFpTjP>l3m^O(P!3bvF+T9KVN5ZwY>_W>GSgJJT+DW;aiR_H4rIl7)JGzLVgc?=C9NyS| z!@9%~)j|^~Jqd~M1St5yG{`f(g7cCz`> zeWc)rQ()}*0e(wmM>)qSDjI + + + + + + Python Module Index — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + + + + +
    +
    +
    +
    + + +

    Python Module Index

    + +
    + e | + w +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    + e
    + examples +
        + examples.datastore_config_server +
     
    + w
    + writer +
        + writer.storage +
        + writer.store_events +
        + writer.upload_codes +
        + writer.writer_app +
    + wsgi +
        + wsgi.rcodes +
        + wsgi.wsgi_app +
    + + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 0000000..9b85abf --- /dev/null +++ b/search.html @@ -0,0 +1,103 @@ + + + + + + + Search — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +

    Search

    + + + + +

    + Searching for multiple words only shows matches that contain + all words. +

    + + +
    + + + +
    + + +
    + + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 0000000..c5fc12a --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"Contents:": [[0, null]], "Indices and tables": [[0, "indices-and-tables"]], "Introduction": [[1, null]], "Storage classes - raw datastore tables": [[2, "module-writer.storage"]], "Store events module": [[2, "module-writer.store_events"]], "Upload codes": [[2, "module-writer.upload_codes"]], "Welcome to HiSPARC-datastore\u2019s documentation!": [[0, null]], "Writer application": [[2, "module-writer.writer_app"]], "XML-RPC-Server": [[4, null]], "datastore WSGI app package": [[3, null]], "datastore writer package": [[2, null]], "wsgi.rcodes module": [[3, "module-wsgi.rcodes"]], "wsgi.wsgi_app module": [[3, "module-wsgi.wsgi_app"]]}, "docnames": ["index", "introduction", "writer", "wsgi", "xml-rpc-server"], "envversion": {"sphinx": 63, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1}, "filenames": ["index.rst", "introduction.rst", "writer.rst", "wsgi.rst", "xml-rpc-server.rst"], "indexentries": {"application() (in module wsgi.wsgi_app)": [[3, "wsgi.wsgi_app.application", false]], "columns (writer.storage.hisparccomparator attribute)": [[2, "writer.storage.HisparcComparator.columns", false]], "columns (writer.storage.hisparcconfiguration attribute)": [[2, "writer.storage.HisparcConfiguration.columns", false]], "columns (writer.storage.hisparcerror attribute)": [[2, "writer.storage.HisparcError.columns", false]], "columns (writer.storage.hisparcevent attribute)": [[2, "writer.storage.HisparcEvent.columns", false]], "columns (writer.storage.hisparcsatellite attribute)": [[2, "writer.storage.HisparcSatellite.columns", false]], "columns (writer.storage.hisparcsingle attribute)": [[2, "writer.storage.HisparcSingle.columns", false]], "columns (writer.storage.lightningconfig attribute)": [[2, "writer.storage.LightningConfig.columns", false]], "columns (writer.storage.lightningerror attribute)": [[2, "writer.storage.LightningError.columns", false]], "columns (writer.storage.lightningevent attribute)": [[2, "writer.storage.LightningEvent.columns", false]], "columns (writer.storage.lightningnoise attribute)": [[2, "writer.storage.LightningNoise.columns", false]], "columns (writer.storage.lightningstatus attribute)": [[2, "writer.storage.LightningStatus.columns", false]], "columns (writer.storage.weatherconfig attribute)": [[2, "writer.storage.WeatherConfig.columns", false]], "columns (writer.storage.weathererror attribute)": [[2, "writer.storage.WeatherError.columns", false]], "columns (writer.storage.weatherevent attribute)": [[2, "writer.storage.WeatherEvent.columns", false]], "data_is_blob() (in module writer.store_events)": [[2, "writer.store_events.data_is_blob", false]], "decode_object() (in module writer.writer_app)": [[2, "writer.writer_app.decode_object", false]], "decode_object() (in module wsgi.wsgi_app)": [[3, "wsgi.wsgi_app.decode_object", false]], "do_init() (in module wsgi.wsgi_app)": [[3, "wsgi.wsgi_app.do_init", false]], "examples.datastore_config_server": [[4, "module-examples.datastore_config_server", false]], "get_or_create_cluster_group() (in module writer.storage)": [[2, "writer.storage.get_or_create_cluster_group", false]], "get_or_create_node() (in module writer.storage)": [[2, "writer.storage.get_or_create_node", false]], "get_or_create_station_group() (in module writer.storage)": [[2, "writer.storage.get_or_create_station_group", false]], "hisparccomparator (class in writer.storage)": [[2, "writer.storage.HisparcComparator", false]], "hisparcconfiguration (class in writer.storage)": [[2, "writer.storage.HisparcConfiguration", false]], "hisparcerror (class in writer.storage)": [[2, "writer.storage.HisparcError", false]], "hisparcevent (class in writer.storage)": [[2, "writer.storage.HisparcEvent", false]], "hisparcsatellite (class in writer.storage)": [[2, "writer.storage.HisparcSatellite", false]], "hisparcsingle (class in writer.storage)": [[2, "writer.storage.HisparcSingle", false]], "is_data_suspicious() (in module wsgi.wsgi_app)": [[3, "wsgi.wsgi_app.is_data_suspicious", false]], "lightningconfig (class in writer.storage)": [[2, "writer.storage.LightningConfig", false]], "lightningerror (class in writer.storage)": [[2, "writer.storage.LightningError", false]], "lightningevent (class in writer.storage)": [[2, "writer.storage.LightningEvent", false]], "lightningnoise (class in writer.storage)": [[2, "writer.storage.LightningNoise", false]], "lightningstatus (class in writer.storage)": [[2, "writer.storage.LightningStatus", false]], "module": [[2, "module-writer", false], [2, "module-writer.storage", false], [2, "module-writer.store_events", false], [2, "module-writer.upload_codes", false], [2, "module-writer.writer_app", false], [3, "module-wsgi", false], [3, "module-wsgi.rcodes", false], [3, "module-wsgi.wsgi_app", false], [4, "module-examples.datastore_config_server", false]], "open_or_create_file() (in module writer.storage)": [[2, "writer.storage.open_or_create_file", false]], "process_data() (in module writer.writer_app)": [[2, "writer.writer_app.process_data", false]], "reload_datastore() (in module examples.datastore_config_server)": [[4, "examples.datastore_config_server.reload_datastore", false]], "store_data() (in module wsgi.wsgi_app)": [[3, "wsgi.wsgi_app.store_data", false]], "store_event() (in module writer.store_events)": [[2, "writer.store_events.store_event", false]], "store_event_list() (in module writer.store_events)": [[2, "writer.store_events.store_event_list", false]], "weatherconfig (class in writer.storage)": [[2, "writer.storage.WeatherConfig", false]], "weathererror (class in writer.storage)": [[2, "writer.storage.WeatherError", false]], "weatherevent (class in writer.storage)": [[2, "writer.storage.WeatherEvent", false]], "writer": [[2, "module-writer", false]], "writer() (in module writer.writer_app)": [[2, "writer.writer_app.writer", false]], "writer.storage": [[2, "module-writer.storage", false]], "writer.store_events": [[2, "module-writer.store_events", false]], "writer.upload_codes": [[2, "module-writer.upload_codes", false]], "writer.writer_app": [[2, "module-writer.writer_app", false]], "wsgi": [[3, "module-wsgi", false]], "wsgi.rcodes": [[3, "module-wsgi.rcodes", false]], "wsgi.wsgi_app": [[3, "module-wsgi.wsgi_app", false]]}, "objects": {"": [[2, 0, 0, "-", "writer"], [3, 0, 0, "-", "wsgi"]], "examples": [[4, 0, 0, "-", "datastore_config_server"]], "examples.datastore_config_server": [[4, 1, 1, "", "reload_datastore"]], "writer": [[2, 0, 0, "-", "storage"], [2, 0, 0, "-", "store_events"], [2, 0, 0, "-", "upload_codes"], [2, 0, 0, "-", "writer_app"]], "writer.storage": [[2, 2, 1, "", "HisparcComparator"], [2, 2, 1, "", "HisparcConfiguration"], [2, 2, 1, "", "HisparcError"], [2, 2, 1, "", "HisparcEvent"], [2, 2, 1, "", "HisparcSatellite"], [2, 2, 1, "", "HisparcSingle"], [2, 2, 1, "", "LightningConfig"], [2, 2, 1, "", "LightningError"], [2, 2, 1, "", "LightningEvent"], [2, 2, 1, "", "LightningNoise"], [2, 2, 1, "", "LightningStatus"], [2, 2, 1, "", "WeatherConfig"], [2, 2, 1, "", "WeatherError"], [2, 2, 1, "", "WeatherEvent"], [2, 1, 1, "", "get_or_create_cluster_group"], [2, 1, 1, "", "get_or_create_node"], [2, 1, 1, "", "get_or_create_station_group"], [2, 1, 1, "", "open_or_create_file"]], "writer.storage.HisparcComparator": [[2, 3, 1, "", "columns"]], "writer.storage.HisparcConfiguration": [[2, 3, 1, "", "columns"]], "writer.storage.HisparcError": [[2, 3, 1, "", "columns"]], "writer.storage.HisparcEvent": [[2, 3, 1, "", "columns"]], "writer.storage.HisparcSatellite": [[2, 3, 1, "", "columns"]], "writer.storage.HisparcSingle": [[2, 3, 1, "", "columns"]], "writer.storage.LightningConfig": [[2, 3, 1, "", "columns"]], "writer.storage.LightningError": [[2, 3, 1, "", "columns"]], "writer.storage.LightningEvent": [[2, 3, 1, "", "columns"]], "writer.storage.LightningNoise": [[2, 3, 1, "", "columns"]], "writer.storage.LightningStatus": [[2, 3, 1, "", "columns"]], "writer.storage.WeatherConfig": [[2, 3, 1, "", "columns"]], "writer.storage.WeatherError": [[2, 3, 1, "", "columns"]], "writer.storage.WeatherEvent": [[2, 3, 1, "", "columns"]], "writer.store_events": [[2, 1, 1, "", "data_is_blob"], [2, 1, 1, "", "store_event"], [2, 1, 1, "", "store_event_list"]], "writer.writer_app": [[2, 1, 1, "", "decode_object"], [2, 1, 1, "", "process_data"], [2, 1, 1, "", "writer"]], "wsgi": [[3, 0, 0, "-", "rcodes"], [3, 0, 0, "-", "wsgi_app"]], "wsgi.wsgi_app": [[3, 1, 1, "", "application"], [3, 1, 1, "", "decode_object"], [3, 1, 1, "", "do_init"], [3, 1, 1, "", "is_data_suspicious"], [3, 1, 1, "", "store_data"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "attribute", "Python attribute"]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:attribute"}, "terms": {"": 4, "0": [2, 3], "00": 3, "1": 2, "10": 2, "100": 3, "11": 2, "12": 2, "13": 2, "14": 2, "15": 2, "16": 2, "17": 2, "18": 2, "19": 2, "2": 2, "20": [2, 3], "201": 3, "2019": 3, "203": 3, "206": 3, "208": 3, "21": 2, "22": 2, "23": 2, "24": 2, "25": 2, "26": 2, "27": 2, "28": 2, "29": 2, "3": 2, "30": [2, 3], "31": 2, "32": 2, "33": 2, "34": 2, "35": 2, "36": 2, "37": 2, "38": 2, "39": 2, "4": 2, "40": [2, 3], "400": 3, "41": 2, "42": 2, "43": 2, "44": 2, "45": 2, "46": 2, "47": 2, "48": 2, "49": 2, "5": [2, 3], "50": 2, "51": 2, "52": 2, "53": 2, "54": 2, "55": 2, "56": 2, "57": 2, "58": 2, "59": 2, "6": 2, "60": 2, "61": 2, "62": 2, "63": 2, "64": 2, "65": 2, "66": 2, "67": 2, "68": 2, "69": 2, "7": [2, 3], "70": 2, "71": 2, "72": 2, "73": 2, "74": 2, "75": 2, "76": 2, "77": 2, "78": 2, "79": 2, "8": 2, "80": 2, "81": 2, "82": 2, "83": 2, "84": 2, "85": 2, "86": 2, "87": 2, "88": 2, "9": 2, "By": 3, "If": 2, "It": [2, 4], "Such": 3, "The": [3, 4], "There": 3, "accord": 2, "acquir": 3, "actual": 3, "after": 3, "all": [2, 3], "alreadi": 3, "altitud": 2, "an": [2, 4], "angle_correct": 2, "app": [0, 2], "append": [2, 3], "applic": [0, 3], "apr": 3, "ar": 3, "bad": 3, "baromet": 2, "baselin": 2, "basi": 4, "baud_rat": 2, "been": 3, "befor": 3, "belong": 2, "belov": 3, "between": [2, 3], "bidirect": 3, "bin": [2, 3], "binari": 2, "birth": 3, "blob": 2, "blob_typ": 2, "boolcol": 2, "buffer": 2, "bytestr": [2, 3], "call": 3, "can": 3, "caus": 3, "channel": 3, "check": 3, "checksum": 3, "class": 0, "close_alarm": 2, "close_alarm_dist": 2, "close_r": 2, "cluster": [2, 3, 4], "code": [0, 1, 3, 4], "coinctim": 2, "cold": 3, "column": 2, "com_port": 2, "combin": 3, "come": 3, "commun": 3, "compar": 2, "config": [2, 3, 4], "configfil": [2, 3], "configu": [2, 3], "configur": [2, 3], "contain": 2, "corr_angl": 2, "corr_dist": 2, "count": 2, "creat": 2, "csv": [1, 2, 3], "current": 3, "current_head": 2, "daemon": 4, "daq": 3, "daq_mod": 2, "data": [2, 3], "data_dir": [2, 3], "data_is_blob": 2, "data_reduct": 2, "database_nam": 2, "datafil": 2, "datastor": [1, 4], "datastore_config_serv": 4, "datastream": 3, "datator": 2, "date": 2, "debug": [2, 3], "decod": [2, 3], "decode_object": [2, 3], "default": 3, "delay_check": 2, "delay_error": 2, "delay_screen": 2, "determin": 2, "detnum": 2, "devic": 2, "dew_point": 2, "dflt": 2, "dictionari": 3, "directori": 2, "do": 3, "do_init": 3, "docstr": 2, "document": [1, 4], "doe": [2, 3], "don": 3, "e": 2, "eigenst": 3, "elimin": 3, "empti": 2, "entir": 3, "environ": 3, "error": [2, 3], "evapotranspir": 2, "even": 3, "event": [0, 3], "event_id": 2, "event_list": [2, 3], "event_r": 2, "everi": 3, "everyth": 3, "exampl": [2, 3, 4], "exist": 2, "ext_timestamp": 2, "extend": 4, "fals": 2, "fate": 3, "file": [2, 3], "filesystem": 2, "first": 3, "float32col": 2, "float64col": 2, "four": 3, "from": [2, 3, 4], "frome": [1, 2, 3], "function": [2, 3], "functool": 3, "g": 2, "gener": [2, 3], "get": 2, "get_or_create_cluster_group": 2, "get_or_create_nod": 2, "get_or_create_station_group": 2, "getev": 3, "global": 3, "gp": 3, "gps_altitud": 2, "gps_latitud": 2, "gps_longitud": 2, "group": 2, "h5": 2, "ha": 3, "handl": 4, "handler": 3, "happili": 3, "hdf5": 2, "heat_index": 2, "help_url": 2, "hisparc": [2, 3, 4], "hisparccompar": 2, "hisparcconfigur": 2, "hisparcerror": 2, "hisparcev": 2, "hisparcsatellit": 2, "hisparcsingl": 2, "http": [3, 4], "humidity_insid": 2, "humidity_outsid": 2, "hunt": 3, "i": [2, 3, 4], "id": 2, "import": [2, 3], "incom": 2, "index": 0, "inform": 3, "ini": [2, 3], "initi": 3, "input": 3, "int16col": 2, "int32col": 2, "integr": 2, "intern": 3, "introduct": 0, "invalid": 3, "is_data_suspici": 3, "know": 3, "latitud": 2, "layout": 4, "length": 2, "librari": 4, "lightningconfig": 2, "lightningerror": 2, "lightningev": 2, "lightningnois": 2, "lightningstatu": 2, "list": [1, 2, 3], "live": 3, "load": [3, 4], "log": [2, 3], "logfil": 1, "logger": 3, "loglevel": [2, 3], "longitud": 2, "mai": 3, "mas_ch1_adc_gain": 2, "mas_ch1_adc_offset": 2, "mas_ch1_comp_gain": 2, "mas_ch1_comp_offset": 2, "mas_ch1_curr": 2, "mas_ch1_gain_neg": 2, "mas_ch1_gain_po": 2, "mas_ch1_high": 2, "mas_ch1_inttim": 2, "mas_ch1_low": 2, "mas_ch1_offset_neg": 2, "mas_ch1_offset_po": 2, "mas_ch1_thres_high": 2, "mas_ch1_thres_low": 2, "mas_ch1_voltag": 2, "mas_ch2_adc_gain": 2, "mas_ch2_adc_offset": 2, "mas_ch2_comp_gain": 2, "mas_ch2_comp_offset": 2, "mas_ch2_curr": 2, "mas_ch2_gain_neg": 2, "mas_ch2_gain_po": 2, "mas_ch2_high": 2, "mas_ch2_inttim": 2, "mas_ch2_low": 2, "mas_ch2_offset_neg": 2, "mas_ch2_offset_po": 2, "mas_ch2_thres_high": 2, "mas_ch2_thres_low": 2, "mas_ch2_voltag": 2, "mas_common_offset": 2, "mas_comp_thres_high": 2, "mas_comp_thres_low": 2, "mas_internal_voltag": 2, "mas_max_voltag": 2, "mas_reset": 2, "mas_vers": 2, "match": 3, "max_n": 2, "max_sign": 2, "mean_n": 2, "mean_sign": 2, "messag": 2, "min_n": 2, "min_sign": 2, "minimum_gps_spe": 2, "modul": 0, "most": 3, "must": 3, "n_peak": 2, "name": 2, "nanosecond": 2, "necessari": 4, "need": 3, "new": 2, "nikef": 3, "nl": 3, "node": 2, "noise_beep": 2, "note": 3, "number": [2, 3], "o": [2, 3], "object": [2, 3], "offset_bar_sea_level": 2, "offset_inside_humid": 2, "offset_inside_temperatur": 2, "offset_outside_humid": 2, "offset_outside_temperatur": 2, "offset_station_altitud": 2, "offset_wind_direct": 2, "ok": 3, "one": [2, 3], "open": 2, "open_or_create_fil": 2, "our": 3, "out": 3, "packag": 0, "page": 0, "paramet": 2, "parent": 2, "partial": 3, "pass": 3, "password": [2, 3, 4], "past": 3, "path": [2, 3], "pickl": 2, "piqu": 1, "po": 2, "poll": 2, "post": 3, "postcoinctim": 2, "precoinctim": 2, "previous": 3, "probabl": 3, "process_data": 2, "pulseheight": 2, "putev": 3, "py": 2, "pytabl": 2, "python": 4, "quantum": 3, "queue": 2, "rain_rat": 2, "raw": [0, 1], "rcode": 0, "read": [2, 3], "readlin": 3, "recurs": [2, 3], "reduce_data": 2, "reload": 4, "reload_datastor": [0, 4], "request": 3, "reread": 3, "return": 3, "reweav": 3, "rip": 4, "rpc": 0, "run": 4, "script": 2, "search": 0, "send": 3, "serv": 3, "server": [0, 3], "set": 3, "setup": 1, "sever_alarm": 2, "severe_alarm_dist": 2, "shape": 2, "share": [2, 3], "should": 4, "signal": 3, "signifi": 3, "simpl": 4, "simplexmlrpcserv": 4, "sleep": [2, 3], "slv_ch1_adc_gain": 2, "slv_ch1_adc_offset": 2, "slv_ch1_comp_gain": 2, "slv_ch1_comp_offset": 2, "slv_ch1_current": 2, "slv_ch1_gain_neg": 2, "slv_ch1_gain_po": 2, "slv_ch1_high": 2, "slv_ch1_inttim": 2, "slv_ch1_low": 2, "slv_ch1_offset_neg": 2, "slv_ch1_offset_po": 2, "slv_ch1_thres_high": 2, "slv_ch1_thres_low": 2, "slv_ch1_voltag": 2, "slv_ch2_adc_gain": 2, "slv_ch2_adc_offset": 2, "slv_ch2_comp_gain": 2, "slv_ch2_comp_offset": 2, "slv_ch2_current": 2, "slv_ch2_gain_neg": 2, "slv_ch2_gain_po": 2, "slv_ch2_high": 2, "slv_ch2_inttim": 2, "slv_ch2_low": 2, "slv_ch2_offset_neg": 2, "slv_ch2_offset_po": 2, "slv_ch2_thres_high": 2, "slv_ch2_thres_low": 2, "slv_ch2_voltag": 2, "slv_common_offset": 2, "slv_comp_thres_high": 2, "slv_comp_thres_low": 2, "slv_internal_voltag": 2, "slv_max_voltag": 2, "slv_reset": 2, "slv_version": 2, "solar_rad": 2, "solar_radi": 2, "someon": 3, "sourc": [2, 3, 4], "spare_byt": 2, "squelch_set": 2, "start": 3, "start_respons": 3, "startmod": 2, "station": [1, 2, 3, 4], "station_id": [2, 3], "station_list": [2, 3], "std_dev": 2, "storag": [0, 3], "store": [0, 3], "store_data": 3, "store_ev": 2, "store_event_list": [2, 3], "suspici": 3, "sy": [2, 3], "t": 3, "tabl": 1, "temp_insid": 2, "temp_outsid": 2, "temperature_insid": 2, "temperature_outsid": 2, "temporari": 3, "thi": [2, 3, 4], "time": 3, "time32col": 2, "timestamp": [2, 3], "tmp": [2, 3], "todo": 1, "total_r": 2, "trace": 2, "trig_and_or": 2, "trig_extern": 2, "trig_high_sign": 2, "trig_low_sign": 2, "trigger_pattern": 2, "try": 3, "type": 3, "u": 3, "uint16col": 2, "uint32col": 2, "uint64col": 2, "uint8col": 2, "uncorr_angl": 2, "uncorr_dist": 2, "univers": 3, "unknown": 3, "unpickl": 3, "up": 3, "updat": 4, "upload": [0, 1, 3], "uploadcod": 2, "url": 3, "us": [2, 3], "use_filt": 2, "use_filter_threshold": 2, "usual": [2, 3], "uv": 2, "uv_index": 2, "uwsgi": 3, "valu": 2, "var": [2, 3], "variabl": [2, 3], "verifi": 3, "wa": 4, "we": 3, "weatherconfig": 2, "weathererror": 2, "weatherev": 2, "when": [3, 4], "whenev": 3, "which": 2, "wind_chil": 2, "wind_dir": 2, "wind_direct": 2, "wind_spe": 2, "without": 3, "wrapper": 2, "write": 2, "writer": [0, 3], "writer_app": 2, "written": 2, "wrong": 3, "wsgi": [0, 2], "wsgi_app": 0, "www": [2, 3], "xml": 0, "yet": [2, 3]}, "titles": ["Welcome to HiSPARC-datastore\u2019s documentation!", "Introduction", "datastore writer package", "datastore WSGI app package", "XML-RPC-Server"], "titleterms": {"": 0, "app": 3, "applic": 2, "class": 2, "code": 2, "content": 0, "datastor": [0, 2, 3], "document": 0, "event": 2, "hisparc": 0, "indic": 0, "introduct": 1, "modul": [2, 3], "packag": [2, 3], "raw": 2, "rcode": 3, "rpc": 4, "server": 4, "storag": 2, "store": 2, "tabl": [0, 2], "upload": 2, "welcom": 0, "writer": 2, "wsgi": 3, "wsgi_app": 3, "xml": 4}}) \ No newline at end of file diff --git a/writer.html b/writer.html new file mode 100644 index 0000000..7b033d7 --- /dev/null +++ b/writer.html @@ -0,0 +1,505 @@ + + + + + + + + datastore writer package — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +

    datastore writer package

    +

    HiSPARC datastore writer application

    +

    This script polls /datatore/frome/incoming for incoming data written +by the WSGI app. It then writes the data into the raw datastore.

    +

    example writer_app.py:

    +
    """Wrapper for the writer application"""
    +
    +import sys
    +
    +sys.path.append('/var/www/wsgi-bin/datastore/')
    +
    +from writer import writer_app
    +
    +configfile = '/var/www/wsgi-bin/datastore/examples/config.ini'
    +writer_app.writer(configfile)
    +
    +
    +

    configuration is read from a configuation file, shared between the WSGI app +and the writer usually config.ini:

    +
    [General]
    +log=/tmp/hisparc.log
    +loglevel=debug
    +station_list=/tmp/station_list.csv
    +data_dir=/tmp/datastore
    +
    +[Writer]
    +sleep=5
    +
    +
    +
    +

    Writer application

    +

    datastore writer application

    +

    This module empties the station data incoming queue and writes the +data into HDF5 files using PyTables.

    +
    +
    +writer.writer_app.decode_object(o)[source]
    +

    recursively decode all bytestrings in object

    +
    + +
    +
    +writer.writer_app.process_data(file, data_dir)[source]
    +

    Read data from a pickled object and store store in raw datastore

    +
    + +
    +
    +writer.writer_app.writer(configfile)[source]
    +

    hisparc datastore writer application

    +

    This script polls /datatore/frome/incoming for incoming data written +by the WSGI app. It then store the data into the raw datastore.

    +

    Configuration is read from the datastore configuation file (usually +config.ini):

    +
    [General]
    +log=/tmp/hisparc.log
    +loglevel=debug
    +station_list=/tmp/station_list.csv
    +data_dir=/tmp/datastore
    +
    +[Writer]
    +sleep=5
    +
    +
    +
    + +
    +
    +

    Storage classes - raw datastore tables

    +

    Storage docstrings

    +
    +
    +class writer.storage.HisparcComparator
    +
    +
    +columns = {'comparator': UInt8Col(shape=(), dflt=0, pos=5), 'count': UInt16Col(shape=(), dflt=0, pos=6), 'device': UInt8Col(shape=(), dflt=0, pos=4), 'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'ext_timestamp': UInt64Col(shape=(), dflt=0, pos=3), 'nanoseconds': UInt32Col(shape=(), dflt=0, pos=2), 'timestamp': Time32Col(shape=(), dflt=0, pos=1)}
    +
    + +
    + +
    +
    +class writer.storage.HisparcConfiguration
    +
    +
    +columns = {'buffer': Int32Col(shape=(), dflt=-1, pos=20), 'coinctime': Float64Col(shape=(), dflt=0.0, pos=12), 'delay_check': Float64Col(shape=(), dflt=0.0, pos=23), 'delay_error': Float64Col(shape=(), dflt=0.0, pos=24), 'delay_screen': Float64Col(shape=(), dflt=0.0, pos=22), 'detnum': UInt16Col(shape=(), dflt=0, pos=14), 'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'gps_altitude': Float64Col(shape=(), dflt=0.0, pos=4), 'gps_latitude': Float64Col(shape=(), dflt=0.0, pos=2), 'gps_longitude': Float64Col(shape=(), dflt=0.0, pos=3), 'mas_ch1_adc_gain': Float64Col(shape=(), dflt=0.0, pos=49), 'mas_ch1_adc_offset': Float64Col(shape=(), dflt=0.0, pos=50), 'mas_ch1_comp_gain': Float64Col(shape=(), dflt=0.0, pos=53), 'mas_ch1_comp_offset': Float64Col(shape=(), dflt=0.0, pos=54), 'mas_ch1_current': Float64Col(shape=(), dflt=0.0, pos=33), 'mas_ch1_gain_neg': UInt8Col(shape=(), dflt=0, pos=40), 'mas_ch1_gain_pos': UInt8Col(shape=(), dflt=0, pos=39), 'mas_ch1_inttime': Float64Col(shape=(), dflt=0.0, pos=29), 'mas_ch1_offset_neg': UInt8Col(shape=(), dflt=0, pos=44), 'mas_ch1_offset_pos': UInt8Col(shape=(), dflt=0, pos=43), 'mas_ch1_thres_high': Float64Col(shape=(), dflt=0.0, pos=26), 'mas_ch1_thres_low': Float64Col(shape=(), dflt=0.0, pos=25), 'mas_ch1_voltage': Float64Col(shape=(), dflt=0.0, pos=31), 'mas_ch2_adc_gain': Float64Col(shape=(), dflt=0.0, pos=51), 'mas_ch2_adc_offset': Float64Col(shape=(), dflt=0.0, pos=52), 'mas_ch2_comp_gain': Float64Col(shape=(), dflt=0.0, pos=55), 'mas_ch2_comp_offset': Float64Col(shape=(), dflt=0.0, pos=56), 'mas_ch2_current': Float64Col(shape=(), dflt=0.0, pos=34), 'mas_ch2_gain_neg': UInt8Col(shape=(), dflt=0, pos=42), 'mas_ch2_gain_pos': UInt8Col(shape=(), dflt=0, pos=41), 'mas_ch2_inttime': Float64Col(shape=(), dflt=0.0, pos=30), 'mas_ch2_offset_neg': UInt8Col(shape=(), dflt=0, pos=46), 'mas_ch2_offset_pos': UInt8Col(shape=(), dflt=0, pos=45), 'mas_ch2_thres_high': Float64Col(shape=(), dflt=0.0, pos=28), 'mas_ch2_thres_low': Float64Col(shape=(), dflt=0.0, pos=27), 'mas_ch2_voltage': Float64Col(shape=(), dflt=0.0, pos=32), 'mas_common_offset': UInt8Col(shape=(), dflt=0, pos=47), 'mas_comp_thres_high': Float64Col(shape=(), dflt=0.0, pos=36), 'mas_comp_thres_low': Float64Col(shape=(), dflt=0.0, pos=35), 'mas_internal_voltage': UInt8Col(shape=(), dflt=0, pos=48), 'mas_max_voltage': Float64Col(shape=(), dflt=0.0, pos=37), 'mas_reset': BoolCol(shape=(), dflt=False, pos=38), 'mas_version': Int32Col(shape=(), dflt=-1, pos=5), 'password': Int32Col(shape=(), dflt=-1, pos=15), 'postcoinctime': Float64Col(shape=(), dflt=0.0, pos=13), 'precoinctime': Float64Col(shape=(), dflt=0.0, pos=11), 'reduce_data': BoolCol(shape=(), dflt=False, pos=19), 'slv_ch1_adc_gain': Float64Col(shape=(), dflt=0.0, pos=81), 'slv_ch1_adc_offset': Float64Col(shape=(), dflt=0.0, pos=82), 'slv_ch1_comp_gain': Float64Col(shape=(), dflt=0.0, pos=85), 'slv_ch1_comp_offset': Float64Col(shape=(), dflt=0.0, pos=86), 'slv_ch1_current': Float64Col(shape=(), dflt=0.0, pos=65), 'slv_ch1_gain_neg': UInt8Col(shape=(), dflt=0, pos=72), 'slv_ch1_gain_pos': UInt8Col(shape=(), dflt=0, pos=71), 'slv_ch1_inttime': Float64Col(shape=(), dflt=0.0, pos=61), 'slv_ch1_offset_neg': UInt8Col(shape=(), dflt=0, pos=76), 'slv_ch1_offset_pos': UInt8Col(shape=(), dflt=0, pos=75), 'slv_ch1_thres_high': Float64Col(shape=(), dflt=0.0, pos=58), 'slv_ch1_thres_low': Float64Col(shape=(), dflt=0.0, pos=57), 'slv_ch1_voltage': Float64Col(shape=(), dflt=0.0, pos=63), 'slv_ch2_adc_gain': Float64Col(shape=(), dflt=0.0, pos=83), 'slv_ch2_adc_offset': Float64Col(shape=(), dflt=0.0, pos=84), 'slv_ch2_comp_gain': Float64Col(shape=(), dflt=0.0, pos=87), 'slv_ch2_comp_offset': Float64Col(shape=(), dflt=0.0, pos=88), 'slv_ch2_current': Float64Col(shape=(), dflt=0.0, pos=66), 'slv_ch2_gain_neg': UInt8Col(shape=(), dflt=0, pos=74), 'slv_ch2_gain_pos': UInt8Col(shape=(), dflt=0, pos=73), 'slv_ch2_inttime': Float64Col(shape=(), dflt=0.0, pos=62), 'slv_ch2_offset_neg': UInt8Col(shape=(), dflt=0, pos=78), 'slv_ch2_offset_pos': UInt8Col(shape=(), dflt=0, pos=77), 'slv_ch2_thres_high': Float64Col(shape=(), dflt=0.0, pos=60), 'slv_ch2_thres_low': Float64Col(shape=(), dflt=0.0, pos=59), 'slv_ch2_voltage': Float64Col(shape=(), dflt=0.0, pos=64), 'slv_common_offset': UInt8Col(shape=(), dflt=0, pos=79), 'slv_comp_thres_high': Float64Col(shape=(), dflt=0.0, pos=68), 'slv_comp_thres_low': Float64Col(shape=(), dflt=0.0, pos=67), 'slv_internal_voltage': UInt8Col(shape=(), dflt=0, pos=80), 'slv_max_voltage': Float64Col(shape=(), dflt=0.0, pos=69), 'slv_reset': BoolCol(shape=(), dflt=False, pos=70), 'slv_version': Int32Col(shape=(), dflt=-1, pos=6), 'spare_bytes': UInt8Col(shape=(), dflt=0, pos=16), 'startmode': BoolCol(shape=(), dflt=False, pos=21), 'timestamp': Time32Col(shape=(), dflt=0, pos=1), 'trig_and_or': BoolCol(shape=(), dflt=False, pos=10), 'trig_external': UInt32Col(shape=(), dflt=0, pos=9), 'trig_high_signals': UInt32Col(shape=(), dflt=0, pos=8), 'trig_low_signals': UInt32Col(shape=(), dflt=0, pos=7), 'use_filter': BoolCol(shape=(), dflt=False, pos=17), 'use_filter_threshold': BoolCol(shape=(), dflt=False, pos=18)}
    +
    + +
    + +
    +
    +class writer.storage.HisparcError
    +

    HiSPARC Error messages tables

    +
    +
    +columns = {'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'messages': Int32Col(shape=(), dflt=-1, pos=2), 'timestamp': Time32Col(shape=(), dflt=0, pos=1)}
    +
    + +
    + +
    +
    +class writer.storage.HisparcEvent
    +
    +
    +columns = {'baseline': Int16Col(shape=(4,), dflt=-1, pos=6), 'data_reduction': BoolCol(shape=(), dflt=False, pos=4), 'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'event_rate': Float32Col(shape=(), dflt=0.0, pos=12), 'ext_timestamp': UInt64Col(shape=(), dflt=0, pos=3), 'integrals': Int32Col(shape=(4,), dflt=-1, pos=10), 'n_peaks': Int16Col(shape=(4,), dflt=-1, pos=8), 'nanoseconds': UInt32Col(shape=(), dflt=0, pos=2), 'pulseheights': Int16Col(shape=(4,), dflt=-1, pos=9), 'std_dev': Int16Col(shape=(4,), dflt=-1, pos=7), 'timestamp': Time32Col(shape=(), dflt=0, pos=1), 'traces': Int32Col(shape=(4,), dflt=-1, pos=11), 'trigger_pattern': UInt32Col(shape=(), dflt=0, pos=5)}
    +
    + +
    + +
    +
    +class writer.storage.HisparcSatellite
    +
    +
    +columns = {'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'max_n': UInt16Col(shape=(), dflt=0, pos=4), 'max_signal': UInt16Col(shape=(), dflt=0, pos=7), 'mean_n': Float32Col(shape=(), dflt=0.0, pos=3), 'mean_signal': Float32Col(shape=(), dflt=0.0, pos=6), 'min_n': UInt16Col(shape=(), dflt=0, pos=2), 'min_signal': UInt16Col(shape=(), dflt=0, pos=5), 'timestamp': Time32Col(shape=(), dflt=0, pos=1)}
    +
    + +
    + +
    +
    +class writer.storage.HisparcSingle
    +
    +
    +columns = {'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'mas_ch1_high': Int32Col(shape=(), dflt=-1, pos=3), 'mas_ch1_low': Int32Col(shape=(), dflt=-1, pos=2), 'mas_ch2_high': Int32Col(shape=(), dflt=-1, pos=5), 'mas_ch2_low': Int32Col(shape=(), dflt=-1, pos=4), 'slv_ch1_high': Int32Col(shape=(), dflt=-1, pos=7), 'slv_ch1_low': Int32Col(shape=(), dflt=-1, pos=6), 'slv_ch2_high': Int32Col(shape=(), dflt=-1, pos=9), 'slv_ch2_low': Int32Col(shape=(), dflt=-1, pos=8), 'timestamp': Time32Col(shape=(), dflt=0, pos=1)}
    +
    + +
    + +
    +
    +class writer.storage.LightningConfig
    +
    +
    +columns = {'altitude': Float64Col(shape=(), dflt=0.0, pos=10), 'angle_correction': Float32Col(shape=(), dflt=0.0, pos=16), 'baud_rate': Int16Col(shape=(), dflt=0, pos=3), 'close_alarm_distance': Int32Col(shape=(), dflt=0, pos=12), 'com_port': UInt8Col(shape=(), dflt=0, pos=2), 'daq_mode': BoolCol(shape=(), dflt=False, pos=7), 'database_name': Int32Col(shape=(), dflt=-1, pos=5), 'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'help_url': Int32Col(shape=(), dflt=-1, pos=6), 'latitude': Float64Col(shape=(), dflt=0.0, pos=8), 'longitude': Float64Col(shape=(), dflt=0.0, pos=9), 'minimum_gps_speed': Int32Col(shape=(), dflt=0, pos=15), 'noise_beep': BoolCol(shape=(), dflt=False, pos=14), 'severe_alarm_distance': Int32Col(shape=(), dflt=0, pos=13), 'squelch_seting': Int32Col(shape=(), dflt=0, pos=11), 'station_id': UInt32Col(shape=(), dflt=0, pos=4), 'timestamp': Time32Col(shape=(), dflt=0, pos=1)}
    +
    + +
    + +
    +
    +class writer.storage.LightningError
    +
    +
    +columns = {'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'messages': Int32Col(shape=(), dflt=-1, pos=2), 'timestamp': Time32Col(shape=(), dflt=0, pos=1)}
    +
    + +
    + +
    +
    +class writer.storage.LightningEvent
    +
    +
    +columns = {'corr_angle': Float32Col(shape=(), dflt=0.0, pos=5), 'corr_distance': Int16Col(shape=(), dflt=0, pos=2), 'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'timestamp': Time32Col(shape=(), dflt=0, pos=1), 'uncorr_angle': Float32Col(shape=(), dflt=0.0, pos=4), 'uncorr_distance': Int16Col(shape=(), dflt=0, pos=3)}
    +
    + +
    + +
    +
    +class writer.storage.LightningNoise
    +
    +
    +columns = {'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'timestamp': Time32Col(shape=(), dflt=0, pos=1)}
    +
    + +
    + +
    +
    +class writer.storage.LightningStatus
    +
    +
    +columns = {'close_alarm': BoolCol(shape=(), dflt=False, pos=4), 'close_rate': Int16Col(shape=(), dflt=0, pos=2), 'current_heading': Float32Col(shape=(), dflt=0.0, pos=6), 'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'sever_alarm': BoolCol(shape=(), dflt=False, pos=5), 'timestamp': Time32Col(shape=(), dflt=0, pos=1), 'total_rate': Int16Col(shape=(), dflt=0, pos=3)}
    +
    + +
    + +
    +
    +class writer.storage.WeatherConfig
    +
    +
    +columns = {'altitude': Float64Col(shape=(), dflt=0.0, pos=10), 'barometer': BoolCol(shape=(), dflt=False, pos=15), 'baud_rate': Int16Col(shape=(), dflt=0, pos=3), 'com_port': UInt8Col(shape=(), dflt=0, pos=2), 'daq_mode': BoolCol(shape=(), dflt=False, pos=7), 'database_name': Int32Col(shape=(), dflt=-1, pos=5), 'dew_point': BoolCol(shape=(), dflt=False, pos=23), 'evapotranspiration': BoolCol(shape=(), dflt=False, pos=20), 'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'heat_index': BoolCol(shape=(), dflt=False, pos=22), 'help_url': Int32Col(shape=(), dflt=-1, pos=6), 'humidity_inside': BoolCol(shape=(), dflt=False, pos=13), 'humidity_outside': BoolCol(shape=(), dflt=False, pos=14), 'latitude': Float64Col(shape=(), dflt=0.0, pos=8), 'longitude': Float64Col(shape=(), dflt=0.0, pos=9), 'offset_bar_sea_level': Float32Col(shape=(), dflt=0.0, pos=31), 'offset_inside_humidity': Int16Col(shape=(), dflt=0, pos=27), 'offset_inside_temperature': Float32Col(shape=(), dflt=0.0, pos=25), 'offset_outside_humidity': Int16Col(shape=(), dflt=0, pos=28), 'offset_outside_temperature': Float32Col(shape=(), dflt=0.0, pos=26), 'offset_station_altitude': Float32Col(shape=(), dflt=0.0, pos=30), 'offset_wind_direction': Int16Col(shape=(), dflt=0, pos=29), 'rain_rate': BoolCol(shape=(), dflt=False, pos=21), 'solar_radiation': BoolCol(shape=(), dflt=False, pos=18), 'station_id': UInt32Col(shape=(), dflt=0, pos=4), 'temperature_inside': BoolCol(shape=(), dflt=False, pos=11), 'temperature_outside': BoolCol(shape=(), dflt=False, pos=12), 'timestamp': Time32Col(shape=(), dflt=0, pos=1), 'uv_index': BoolCol(shape=(), dflt=False, pos=19), 'wind_chill': BoolCol(shape=(), dflt=False, pos=24), 'wind_direction': BoolCol(shape=(), dflt=False, pos=16), 'wind_speed': BoolCol(shape=(), dflt=False, pos=17)}
    +
    + +
    + +
    +
    +class writer.storage.WeatherError
    +
    +
    +columns = {'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'messages': Int32Col(shape=(), dflt=-1, pos=2), 'timestamp': Time32Col(shape=(), dflt=0, pos=1)}
    +
    + +
    + +
    +
    +class writer.storage.WeatherEvent
    +
    +
    +columns = {'barometer': Float32Col(shape=(), dflt=0.0, pos=6), 'dew_point': Float32Col(shape=(), dflt=0.0, pos=14), 'evapotranspiration': Float32Col(shape=(), dflt=0.0, pos=11), 'event_id': UInt32Col(shape=(), dflt=0, pos=0), 'heat_index': Int16Col(shape=(), dflt=0, pos=13), 'humidity_inside': Int16Col(shape=(), dflt=0, pos=4), 'humidity_outside': Int16Col(shape=(), dflt=0, pos=5), 'rain_rate': Float32Col(shape=(), dflt=0.0, pos=12), 'solar_rad': Int16Col(shape=(), dflt=0, pos=9), 'temp_inside': Float32Col(shape=(), dflt=0.0, pos=2), 'temp_outside': Float32Col(shape=(), dflt=0.0, pos=3), 'timestamp': Time32Col(shape=(), dflt=0, pos=1), 'uv': Int16Col(shape=(), dflt=0, pos=10), 'wind_chill': Float32Col(shape=(), dflt=0.0, pos=15), 'wind_dir': Int16Col(shape=(), dflt=0, pos=7), 'wind_speed': Int16Col(shape=(), dflt=0, pos=8)}
    +
    + +
    + +
    +
    +writer.storage.get_or_create_cluster_group(file, cluster)[source]
    +

    Get an existing cluster group or create a new one

    +
    +
    Parameters:
    +
      +
    • file – the PyTables data file

    • +
    • cluster – the name of the cluster

    • +
    +
    +
    +
    + +
    +
    +writer.storage.get_or_create_node(file, cluster, node)[source]
    +

    Get an existing node or create a new one

    +
    +
    Parameters:
    +
      +
    • file – the PyTables data file

    • +
    • cluster – the parent (cluster) node

    • +
    • node – the node (e.g. events, blobs)

    • +
    +
    +
    +
    + +
    +
    +writer.storage.get_or_create_station_group(file, cluster, station_id)[source]
    +

    Get an existing station group or create a new one

    +
    +
    Parameters:
    +
      +
    • file – the PyTables data file

    • +
    • cluster – the name of the cluster

    • +
    • station_id – the station number

    • +
    +
    +
    +
    + +
    +
    +writer.storage.open_or_create_file(data_dir, date)[source]
    +

    Open an existing file or create a new one

    +

    This function opens an existing PyTables file according to the event +date. If the file does not yet exist, a new one is created.

    +
    +
    Parameters:
    +
      +
    • data_dir – the directory containing all data files

    • +
    • date – the event date

    • +
    +
    +
    +
    + +
    +
    +

    Store events module

    +
    +
    +writer.store_events.data_is_blob(uploadcode, blob_types)[source]
    +

    Determine if data is a variable length binary value (blob)

    +
    + +
    +
    +writer.store_events.store_event(datafile, cluster, station_id, event)[source]
    +

    Stores an event in the h5 filesystem

    +
    +
    Parameters:
    +
      +
    • datafile – the h5 data file

    • +
    • cluster – the name of the cluster to which the station belongs

    • +
    • station_id – the id of the station this event belongs to

    • +
    • event – the event to store

    • +
    +
    +
    +
    + +
    +
    +writer.store_events.store_event_list(data_dir, station_id, cluster, event_list)[source]
    +

    Store a list of events

    +
    + +
    +
    +

    Upload codes

    +
    +
    + + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/wsgi.html b/wsgi.html new file mode 100644 index 0000000..9407b34 --- /dev/null +++ b/wsgi.html @@ -0,0 +1,249 @@ + + + + + + + + datastore WSGI app package — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +

    datastore WSGI app package

    +

    HiSPARC datastore WSGI application

    +

    This WSGI app is served by uWSGI on frome and lives at +http://frome.nikef.nl/hisparc/upload.

    +

    example application.wsgi:

    +
    import sys
    +import functools
    +
    +sys.path.append('/var/www/wsgi-bin/datastore')
    +
    +from wsgi import wsgi_app
    +
    +configfile = '/var/www/wsgi-bin/datastore/examples/config.ini'
    +application = functools.partial(wsgi_app.application, configfile=configfile)
    +
    +
    +

    configuration is read from a configuation file, shared between the WSGI app +and the writer usually config.ini:

    +
    [General]
    +log=/tmp/hisparc.log
    +loglevel=debug
    +station_list=/tmp/station_list.csv
    +data_dir=/tmp/datastore
    +
    +[Writer]
    +sleep=5
    +
    +
    +
    +

    wsgi.rcodes module

    +

    Return codes for the WSGI app.

    +

    There are four types of return codes:

    +
      +
    1. OK (100)

    2. +
    3. putEvent errors (20*)

      +
        +
      • 201: invalid input: checksum of data does not match

      • +
      • 203: wrong password

      • +
      • 206: station unknown (check station-list.csv)

      • +
      • 208: unpickling error

      • +
      +
    4. +
    5. getEvent errors (30*)

      +

      not used by WSGI app

      +
    6. +
    7. Internal server error (40*)

      +
        +
      • 400: internal server error. Invalid post data.

      • +
      +
    8. +
    +
    +
    +

    wsgi.wsgi_app module

    +
    +
    +wsgi.wsgi_app.application(environ, start_response, configfile)[source]
    +

    The hisparc upload application

    +

    This handler is called by uWSGI whenever someone requests our URL.

    +

    First, we generate a dictionary of POSTed variables and try to read out the +station_id, password, checksum and data. When we do a readline(), we +already read out the entire datastream. I don’t know if we can first check +on station_id/password combinations before reading out the datastream +without setting up a bidirectional communication channel.

    +

    When the checksum matches, we unpickle the event_list and pass everything +on to store_event_list.

    +
    + +
    +
    +wsgi.wsgi_app.decode_object(o)[source]
    +

    recursively decode all bytestrings in object

    +
    + +
    +
    +wsgi.wsgi_app.do_init(configfile)[source]
    +

    Load configuration and passwords and set up a logger handler

    +

    This function will do one-time initialization. By using global +variables, we eliminate the need to reread configuration and passwords +on every request.

    +

    Configuration is read from the datastore configuation file (usually +config.ini):

    +
    [General]
    +log=/tmp/hisparc.log
    +loglevel=debug
    +station_list=/tmp/station_list.csv
    +data_dir=/tmp/datastore
    +
    +[Writer]
    +sleep=5
    +
    +
    +

    Station information is read from the station_list config variable. +(station_list.csv on frome)

    +
    + +
    +
    +wsgi.wsgi_app.is_data_suspicious(event_list)[source]
    +

    Check data for suspiciousness

    +

    Suspiciousness, a previously unknown quantum number that may signify +the actual birth of the universe and the reweaving of past fates into +current events has come to hunt us and our beloved data.

    +

    Note: Apr 7, 2019 0:00 is the default time after a cold start without GPS signal. +The DAQ will happily send events even when no GPS signal has been +acquired (yet). Events with timestamp Apr 7, 2019 are most probably caused +by no or bad GPS signal. Such events must be eigenstates of suspiciousness.

    +
    + +
    +
    +wsgi.wsgi_app.store_data(station_id, cluster, event_list)[source]
    +

    Store verified event data to temporary storage

    +
    + +
    +
    + + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/xml-rpc-server.html b/xml-rpc-server.html new file mode 100644 index 0000000..018d3e5 --- /dev/null +++ b/xml-rpc-server.html @@ -0,0 +1,125 @@ + + + + + + + + XML-RPC-Server — HiSPARC-datastore 1.0.0 documentation + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +

    XML-RPC-Server

    +

    Simple XML-RPC Server to run on the datastore server.

    +

    This daemon should be run on HiSPARC’s datastore server. It will +handle the cluster layouts and station passwords. When an update is +necessary, it will reload the HTTP daemon.

    +

    The basis for this code was ripped from the python SimpleXMLRPCServer +library documentation and extended.

    +
    +
    +examples.datastore_config_server.reload_datastore()[source]
    +

    Load datastore config and reload datastore, if necessary

    +
    + +
    + + +
    +
    +
    +
    + +
    +
    + + + + \ No newline at end of file