Imageboard
A Linkspace Application Tutorial
Available in the pkg or in repository/examples
- placing images
#!/bin/bash set -xeuo pipefail if [ $# -lt 4 ]; then echo "Usage: img_file board_name X Y" exit 2 fi IMG_FILE=$1; BOARD=$2; X=$3; Y=$4; shift 4 IMG_HASH=$(\ cat "$IMG_FILE" \ | lk data --write db --write stdout \ | lk pktf "[hash:str]") TAG=$(printf "%08d%08d" "$X" "$Y") lk link "imageboard:$LK_GROUP:/$BOARD" \ -l "$TAG":"$IMG_HASH" "$@" \ --write db --write stdout \ | lk pktf echo PIDS $(jobs -p)
- viewing images
#!/bin/bash set -euo pipefail BOARD=${1?Usage: board_name [start_stamp] } if [[ ! -f $BOARD.png ]]; then magick convert -size 1000x1000 xc:transparent PNG32:$BOARD.png # Create empty canvas fi START_STAMP=${2:-"0"} # If no stamp is given we begin at 0, i.e. unix epoch in microseconds # We select everything with a create field greater or equal to $START_STAMP lk watch --db-only "imageboard:$LK_GROUP:/$BOARD" -- "create:>=:[u64:$START_STAMP]" \ | lk pktf "[/links:[tag:str] [ptr:str]]" \ | while read REF; do X=${REF:0:8} Y=${REF:8:8} IMG_HASH=${REF: -43} echo "Placing $IMG_HASH at $X , $Y" lk watch-hash $IMG_HASH --ttl 5s \ | lk pktf "[data]" --delimiter "" \ | magick composite -geometry +$X+$Y - PNG32:$BOARD.png PNG32:$BOARD.png done echo "$BOARD: $START_STAMP"
- streaming images
#!/bin/bash -x set -euo pipefail BINS="$(dirname "${BASH_SOURCE[0]}")" LK_DOMAIN="imageboard" # set the default domain LK_GROUP=${LK_GROUP:-"[#:pub]"} BOARD=${1?Usage: board_name [start_stamp] } magick convert -size 1000x1000 xc:transparent PNG32:$BOARD.png # not strictly necessary, but otherwise pull does nothing lk status watch exchange $LK_GROUP process --write "stdout-expr:exchange - [data]" || echo "No exchange process active" echo Pulling $LK_GROUP $BOARD lk pull "imageboard:$LK_GROUP:/$BOARD" --follow -- "create:>:[now:-1D]" $BINS/imageboard.view.sh $BOARD 0 # run once #On receiving a new packet of interest we repaint the board from that stamp lk watch --new-only "imageboard:$LK_GROUP:/$BOARD" | \ lk pktf "[create:str]" | \ while read STAMP; do $BINS/imageboard.view.sh $BOARD $STAMP done # we could use lk watch ::/$BOARD as both the LK_DOMAIN and LK_GROUP were set.
This is a straight-up translation of the bash script. It works, but it could be done better by having only a single python instance running.
- placing images
#!/bin/env python3 import os from linkspace import * import sys if len(sys.argv) < 5: sys.exit('Usage: imagefile boardname x y') [imagefile,boardname,x,y] = sys.argv[1:] x = int(x) y = int(y) if x > 1000 or y > 1000: sys.exit('X and Y coordinates should be < 1000') imgdata = open(imagefile,'rb').read() # this will error if the file is to large ( 2^16 - 512 ). # To use bigger files you can manually split/merge them, or wait for a convention to stabilize datap = lk_datapoint(imgdata) # we can access the point's fields as bytes such as datap.hash, turn those into b64 # print("Saving image ",base64(datap.hash)) # Alternatively we can use lk_eval/lk_eval2str and use an abe expr print(lk_eval2str("Using image [hash:str]",datap)) # We make up this scheme for our app # Tags will be decimal encoded, ptr will point to image data. tag = f"{x:08d}{y:08d}".encode() # Everything in linkspace is plain bytes links = [Link(tag,datap.hash)] linkp = lk_linkpoint(domain=b"imageboard", path=[boardname.encode()], links=links) # print(lk_eval2str("Placing new image [pkt]",linkp)) # instance looks for 'path' arg | $LK_DIR env | $HOME/linkspace lk = lk_open(create=True) # write the point to the linkspace instance _isnew = lk_save(lk,datap) lk_save(lk,linkp)
- viewing images
#!/bin/env python3 from linkspace import * import os import sys if len(sys.argv) < 2: sys.exit('Usage: boardname ?stamp') boardfile = sys.argv[1] + ".png" boardname = sys.argv[1] create_stamp = int(sys.argv[2]) if len(sys.argv) > 2 else 0 if not os.path.exists(boardfile): os.system(f"magick convert -size 1000x1000 xc:transparent PNG32:{boardfile}") lk = lk_open(create=True) group_expr = os.environ.get("LK_GROUP","[#:pub]") group = lk_eval(group_expr) # You can parse multiple statements as abe. # The usual ABE scope is available, and you can extend it with argv query_string = """ domain:=:imageboard group:=:[0] spacename:=:/[1] create:>=:[2/u64] """ query = lk_query_parse(lk_query(),query_string,argv=[group_expr,boardname,str(create_stamp)]) # or use templates. if you're just interested in the string query = lk_query_parse(query,f"create:>=:[:{str(create_stamp)}/u64]") # Or if you have the exact bytes create_b = create_stamp.to_bytes(8,byteorder='big') query = lk_query_parse(query,f"create:>=:[0]",argv=[create_b]) # Or if you're only adding a single statement query = lk_query_push(query,"create",">=",create_b) # The query merges overlapping predicates, and errors on conflicting predicates # Query parsing is somewhat forgiving in that it allows alternative encodings for Group, Domain, and Space. # Group can take the b64 no-pad string # Domain is 16 bytes but does not have to prepend '\0' # Space takes either a '/' delimited expression, or the 'space' bytes ( as given by the spacename function or pkt.spacename value ) # The other values require the exact number of bytes, in big endian when a number. # Its worth understanding why these two work. Checkout the guide create_abe = f"[u64:{str(create_stamp)}]" assert create_b == lk_eval(create_abe) assert lk_encode(create_b,"u64") == create_abe # we'll collect our entries in here image_data = [] def update_image(pkt): create = pkt.create # all the links in the packet will have this as their z-index for link in pkt.links: x = int(str(link.tag[:8],'ascii')) y = int(str(link.tag[8:],'ascii')) q = lk_hash_query(link.ptr) # shorthand for :mode:hash-asc i:=:[u32:0] hash:=:HASH q = lk_query_push(q,"recv","<",lk_eval("[now:+3s]")) # we need a uniq id to register this query under. qid = bytearray(pkt.hash) qid.extend(link.ptr) q = lk_query_push(q,"","qid",bytes(qid)) # print("Looking for ",lk_query_print(q,True)) # we could get with 'lk_get(lk,q)' but to give new packets a chance to arrive we've set recv < now+3s so we will watch them. lk_watch(lk,q,lambda data_pkt : image_data.append([create,x,y,data_pkt,pkt])) # we only care about the ones we know right now. lk_get_all(lk,query, update_image) # Because we set a timeout ( recv<now+5s ) for all data packets ( in case we're still receiving them ) # we can simply wait until all callbacks are done or dropped. lk_process_while(lk) image_data.sort() import pathlib pathlib.Path("./fragments").mkdir(parents=True, exist_ok=True) for [_,x,y,datap,parent] in image_data: filename = lk_eval2str("./fragments/[hash:str]",datap) try: with open(filename, "bx") as f: f.write(datap.data) f.flush() f.close() except Exception as e: pass print(f"placing at {x},{y} the image {filename}") os.system(f"magick composite -geometry +{x}+{y} {filename} PNG32:{boardfile} PNG32:{boardfile}")
- streaming images
#!/bin/env python3 from linkspace import * import os import sys if len(sys.argv) < 2: sys.exit('Usage: boardname ?stamp') boardname = sys.argv[1] create_stamp = int(sys.argv[2]) if len(sys.argv) > 2 else 0 lk = lk_open(create=True) ok = lk_status_watch(lk, qid=b"ex", timeout=lk_eval("[us:+2s]"), domain=b"exchange", objtype=b"process") if not ok and lk_process_while(lk,qid=b"ex") == 0: sys.exit("No exchange process active?") # not strictly necessary, but otherwise pull does nothing else: print("Exchange ok"); query_string = """ group:=:[#:pub] domain:=:imageboard spacename:=:/[0] create:>=:[now:-1D] :qid:[0] """ query = lk_query_parse(lk_query(),query_string,argv=[boardname]) #We signal the exchange process to gather the data lk_pull(lk,query) #we wait for every packet and redraw the painting starting at the 'create' stamp script_dir = os.path.dirname(os.path.realpath(__file__)) os.system(f"{script_dir}/imageboard.view.py {boardname} 0") def update_img(pkt): create = lk_eval2str("[create:str]",pkt) os.system(f"{script_dir}/imageboard.view.py {boardname} {create}") query = lk_query_parse(query,"i_db:<:[u32:0]") # we only care for packets not currently in the database. The new stuff lk_watch(lk,query, update_img) lk_process_while(lk)